GCD 多線程安全 單寫多讀

首先我們從一到線程安全的題目進(jìn)入

有人問(wèn)我這個(gè)題目,其實(shí)這就是在考多線程情況下,多讀一寫的解決方案

解決方案與原理

ARC版本

_ioQueue = dispatch_queue_create("ioQueue", DISPATCH_QUEUE_CONCURRENT);

- (void)setSafeObject:(id)object forKey:(NSString*)key

{

key = [keycopy];

dispatch_barrier_async(self.ioQueue, ^{if(key && object) {

[_dic setObject:object forKey:key];

}

});

}

- (id)getSafeObjectForKey:(NSString*)key

{

__blockidresult = nil;dispatch_sync(self.ioQueue, ^{

result = [_dic objectForKey:key];

});returnresult;

}

首先,我們需要?jiǎng)?chuàng)建一個(gè)私有的并行隊(duì)列來(lái)處理讀寫操作。

在這里不應(yīng)該使用globe_queue, 因?yàn)槲覀兺ㄟ^(guò)dispatch_barrier_async來(lái)保證寫操作的互斥,我們不希望寫操作阻塞住globe_queue中的其他不相關(guān)任務(wù),我們只希望在寫的同時(shí),不會(huì)有其他的寫操作或者讀操作。

同時(shí),也不推薦給隊(duì)列設(shè)置優(yōu)先級(jí),多數(shù)情況下使用default就可以了。而改變優(yōu)先級(jí)往往會(huì)造成一些無(wú)法預(yù)料的問(wèn)題,比如優(yōu)先級(jí)反轉(zhuǎn)(具體的可以參看參考文獻(xiàn))。

dispatch_barrier_async的block運(yùn)行時(shí)機(jī)是,在它之前所有的任務(wù)執(zhí)行完畢,并且在它后面的任務(wù)開(kāi)始之前,期間不會(huì)有其他的任務(wù)執(zhí)行。注意在barrier執(zhí)行的時(shí)候,隊(duì)列本質(zhì)上如同一個(gè)串行隊(duì)列,其執(zhí)行完以后才會(huì)恢復(fù)到并行隊(duì)列。


另外一個(gè)值得注意的問(wèn)題是,在寫操作的時(shí)候,我們使用dispatch_async,而在讀操作的時(shí)候我們使用dispatch_sync。很明顯,這2個(gè)操作一個(gè)是異步的,一個(gè)是同步的。我們不需要使每次程序執(zhí)行的時(shí)候都等待寫操作完成,所以寫操作異步執(zhí)行,但是我們需要同步的執(zhí)行讀操作來(lái)保證程序能夠立刻得到它想要的值。

另外一個(gè)值得注意的問(wèn)題是,在寫操作的時(shí)候,我們使用dispatch_async,而在讀操作的時(shí)候我們使用dispatch_sync。很明顯,這2個(gè)操作一個(gè)是異步的,一個(gè)是同步的。我們不需要使每次程序執(zhí)行的時(shí)候都等待寫操作完成,所以寫操作異步執(zhí)行,但是我們需要同步的執(zhí)行讀操作來(lái)保證程序能夠立刻得到它想要的值。

使用sync的時(shí)候需要極其的小心,因?yàn)樯圆蛔⒁?,就有可能產(chǎn)生死鎖,這可能造成災(zāi)難性的后果。你肯定也注意到了在寫操作的時(shí)候?qū)ey進(jìn)行了copy, 關(guān)于此處的解釋,插入一段來(lái)自參考文獻(xiàn)的引用:

函數(shù)調(diào)用者可以自由傳遞一個(gè)NSMutableString的key,并且能夠在函數(shù)返回后修改它。因此我們必須對(duì)傳入的字符串使用copy操作以確保函數(shù)能夠正確地工作。如果傳入的字符串不是可變的(也就是正常的NSString類型),調(diào)用copy基本上是個(gè)空操作。

到這里整個(gè)基本示例代碼已經(jīng)完成,一般情況下能夠滿足我們的需要。下面來(lái)看看在MRC過(guò)程中我遇到的一些問(wèn)題。

關(guān)于死鎖

dispatch_queue_tqueueA;// 串行隊(duì)列dispatch_sync(queueA, ^(){dispatch_sync(queueA, ^(){

foo();

});

});

造成死鎖比較常見(jiàn)的情況可以簡(jiǎn)化成上面這段代碼。

dispatch_sync會(huì)同步的提交工作并在返回前等待其完成。第一dispatch_sync正在運(yùn)行并等待它的block完成,但是block不能夠完成,它調(diào)用了第二個(gè)dispatch_sync,而第二個(gè)dispatch_sync會(huì)等待串行隊(duì)列中已經(jīng)存在的第一個(gè)任務(wù)完成,很明顯這個(gè)任務(wù)無(wú)法完成,造成死鎖。

值得注意的是main_queue就是一個(gè)串行隊(duì)列。

MRC下容易遇到的問(wèn)題與解決方案

- (void)setSafeObject:(id)object forKey:(NSString*)key

{

key = [keycopy];

dispatch_barrier_async(self.ioQueue, ^{if(key && object) {

[_dic setObject:object forKey:key];

}

});

[key release];

}

- (id)getSafeObjectForKey:(NSString*)key

{

__blockidresult = nil;dispatch_sync(self.ioQueue, ^{

result = [_dic objectForKey:key];

});returnresult;

}

首先我們看看上面這段代碼,基本就是ARC版本轉(zhuǎn)換過(guò)來(lái)的,看起來(lái)沒(méi)問(wèn)題。那么究竟是不是真的沒(méi)問(wèn)題,我們跑段代碼試試看:

//版本一- (void)test

{for(inti =0; i <1000000; i++) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

[selfsetSafeObject:[NSStringstringWithFormat:@"86+131633829%i", i] forKey:KEY];

});NSString*result = [selfgetSafeObjectForKey:KEY];NSLog(@"get string: %@, length : %lu", result, result.length);

}

}

test執(zhí)行后,很快就會(huì)發(fā)生crash,讀操作的result會(huì)發(fā)生野指針。

如果你有經(jīng)驗(yàn)的話,可能會(huì)發(fā)現(xiàn)問(wèn)題:

如果某個(gè)線程a剛?cè)〕隽藃esult值,這次線程b開(kāi)始執(zhí)行寫操作,造成線程a中的result值成為了一份過(guò)期的數(shù)據(jù),如果正好線程b的runloop結(jié)束,很有可能舊的result內(nèi)存地址被釋放掉,這時(shí)線程a中的result就會(huì)發(fā)生野指針crash。

這時(shí)候,你可能會(huì)采取這樣子的修改,代碼如下:

//版本二- (void)test

{for(inti =0; i <1000000; i++) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

[selfsetSafeObject:[NSStringstringWithFormat:@"86+131633829%i", i] forKey:KEY];

});NSString*result = [[selfgetSafeObjectForKey:KEY] retain];NSLog(@"get string: %@, length : %lu", result, result.length);

[result release];

}

}

運(yùn)行之后會(huì)發(fā)現(xiàn),仍然會(huì)crash,其實(shí)問(wèn)題和上面一樣,我們的改動(dòng)沒(méi)有真正的解決問(wèn)題。最好的解決方案是在讀操作之前就已經(jīng)retain住了,看看最終版的代碼吧:

//最終版- (id)getSafeObjectForKey:(NSString*)key

{

__blockidresult = nil;dispatch_sync(self.ioQueue, ^{

result = [[_dic objectForKey:key] retain];

});return[result autorelease];

}

注意retain過(guò)一定要釋放掉,不然或造成內(nèi)存泄露。

再次驗(yàn)證后發(fā)現(xiàn),程序不會(huì)crash了。

GCD是一套很好用的多線程庫(kù),更多的用法請(qǐng)看參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容