寫在開頭:
本文旨在闡述一些大家容易產(chǎn)生迷惑的GCD相關(guān)內(nèi)容,如果是需要了解一些GCD概念或者基礎(chǔ)用法,可以看看這兩篇文章:GCD 掃盲篇、巧談GCD
六敬。
目錄:
迷惑一:隊(duì)列和線程的關(guān)系
迷惑二:GCD的死鎖
迷惑三:以下這些API的異同與作用場(chǎng)景:
dispatch_async
、dispatch_sync
驾荣、dispatch_barrier_async
外构、dispatch_barrier_sync
迷惑一:隊(duì)列和線程的關(guān)系
錯(cuò)誤理解:
- 有些人會(huì)產(chǎn)生一種錯(cuò)覺(jué)普泡,覺(jué)得隊(duì)列就是線程。又有些人會(huì)有另外一種錯(cuò)覺(jué)审编,一個(gè)追加Block就是一個(gè)線程撼班。
正確理解:
對(duì)我們使用者來(lái)說(shuō),**與其說(shuō)GCD是面向線程的垒酬,不如說(shuō)是面向隊(duì)列的砰嘁。 **它隱藏了內(nèi)部線程的調(diào)度。
- 我們所做的僅僅是創(chuàng)建不同的隊(duì)列勘究,把Block追加到隊(duì)列中去執(zhí)行矮湘,而隊(duì)列是FIFO(先進(jìn)先出)的。
- 它會(huì)按照我們追加的Block的順序口糕,在綜合我們調(diào)用的gcd的api(sync板祝、async、dispatch_barrier_async等等)走净,以及根據(jù)系統(tǒng)負(fù)載來(lái)增減線程并發(fā)數(shù), 來(lái)調(diào)度線程執(zhí)行Block孤里。
引用陳宜龍大神文中的一段話:
在 iOS7 時(shí)伏伯,使用 GCD 的并行隊(duì)列, dispatch_async
最大開啟的線程一直能控制在6捌袜、7條说搅,線程數(shù)都是個(gè)位數(shù),然而 iOS8后虏等,最大線程數(shù)一度可以達(dá)到40條弄唧、50條。
更多更進(jìn)階的內(nèi)容霍衫,感興趣的小伙伴可以看看原文:Parse的底層多線程處理思路:GCD高級(jí)用法
我們來(lái)看看以下幾個(gè)例子:
例一:我們?cè)谥骶€程中候引,往一個(gè)并行queue,以sync的方式提交了一個(gè)Block敦跌,結(jié)果Block在主線程中執(zhí)行澄干。
dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue1, ^{
NSLog(@"%@",[NSThread currentThread]);
});
輸出結(jié)果:<NSThread: 0x60000007fc80>{number = 1, name = main}
例二:我們?cè)谥骶€程中用aync方式提交一個(gè)Block,結(jié)果Block在分線程中執(zhí)行柠傍。
dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"%@",[NSThread currentThread]);
});
輸出結(jié)果:<NSThread: 0x7fea68607750>{number = 2, name = (null)}
例三:我們分別用sync和async向主隊(duì)列提交Block麸俘,結(jié)果Block都是在主線程中執(zhí)行:
注意:我們不能直接在主線程用sync如下的形式去提交Block,否則會(huì)引起死鎖:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
我們用sync如下的方式去提交Block:
dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
});
輸出結(jié)果:<NSThread: 0x60000007fc80>{number = 1, name = main}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
輸出結(jié)果:<NSThread: 0x60000007fc80>{number = 1, name = main}
總結(jié)一下:
- 往主隊(duì)列提交Block惧笛,無(wú)論是sync从媚,還是async,都是在主線程中執(zhí)行患整。
- 往非主隊(duì)列中提交拜效,如果是sync喷众,會(huì)在當(dāng)前提交Block的線程中執(zhí)行。如果是async拂檩,則會(huì)在分線程中執(zhí)行侮腹。
上文需要注意以下兩點(diǎn):
- 這里的
sync
,async
并不局限于dispatch_sync
稻励、dispatch_async
父阻,而指的是GCD中所有同步異步的API。 - 這里我們用的是并行queue望抽,如果用串行queue加矛,結(jié)果也是一樣的。唯一的區(qū)別是并行queue會(huì)權(quán)衡當(dāng)前系統(tǒng)負(fù)載煤篙,去同時(shí)并發(fā)幾條線程去執(zhí)行Block斟览,而串行queue中,始終只會(huì)在同一條線程中執(zhí)行Block辑奈。
迷惑二:GCD的死鎖
因?yàn)楹芏嗳艘驗(yàn)椴焕斫獍l(fā)生死鎖的原因苛茂,所以導(dǎo)致從不會(huì)去用sync相關(guān)的API,而sync的應(yīng)用場(chǎng)景還是非常多的鸠窗,我們不能因噎廢食妓羊,所以我們了解死鎖原理還是很重要的。
簡(jiǎn)單舉個(gè)死鎖例子:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)一");
});
NSLog(@"任務(wù)二");
首先造成死鎖的原因很簡(jiǎn)單稍计,兩個(gè)任務(wù)間互相等待躁绸。
為了加深大家的理解,在這里我們盡可能用最詳盡臣嚣,同時(shí)也有點(diǎn)繞的方式總結(jié)下這個(gè)死鎖的流程:
- 如上净刮,在主線程中,往主隊(duì)列同步提交了任務(wù)一硅则。因?yàn)橥鵴ueue中提交Block淹父,總是追加在隊(duì)列尾部的,而queue執(zhí)行Block的順序?yàn)橄冗M(jìn)先出(FIFO)抢埋,所以任務(wù)一需要在當(dāng)前隊(duì)列執(zhí)行完它之前所有的任務(wù)(例如任務(wù)二)弹灭,才能輪到它被執(zhí)行。(注意揪垄,這里引起死鎖并不是因?yàn)槿蝿?wù)二穷吮,哪怕刪去任務(wù)二,這里仍然會(huì)死鎖饥努。這里只是為了舉例說(shuō)明捡鱼,看很多人都在費(fèi)解這一點(diǎn),特此說(shuō)明...)
- 而任務(wù)二因?yàn)槿蝿?wù)一的sync酷愧,被阻塞了驾诈,它需要等任務(wù)一執(zhí)行完才能被執(zhí)行缠诅。兩者互相等待對(duì)方執(zhí)行完,才能執(zhí)行乍迄,程序被死鎖在這了管引。
- 這里需要注意這里死鎖的很重要一個(gè)條件也因?yàn)?strong>主隊(duì)列是一個(gè)串行的隊(duì)列(主隊(duì)列中只有一條主線程)。如果我們?nèi)缦吕沉剑诓⑿嘘?duì)列中提交褥伴,則不會(huì)造成死鎖:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)一");
});
NSLog(@"任務(wù)二");
});
原因是并行隊(duì)列中任務(wù)一雖被提交仍然是在queue的隊(duì)尾,在任務(wù)二之后漾狼,但是因?yàn)槭遣⑿械闹芈匀蝿?wù)一并不會(huì)一直等任務(wù)二結(jié)束才去執(zhí)行,而是直接執(zhí)行完逊躁。此時(shí)任務(wù)二的因?yàn)槿蝿?wù)一的結(jié)束似踱,sync阻塞也就消除,任務(wù)二得以執(zhí)行稽煤。
上述第一個(gè)死鎖的例子核芽,我們很簡(jiǎn)單的改寫一下,死鎖就被消除了:
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)一");
});
NSLog(@"任務(wù)二");
我們?cè)谥骶€程中酵熙,往全局隊(duì)列同步提交了Block狞洋,因?yàn)槿株?duì)列和主隊(duì)列是兩個(gè)隊(duì)列,所以任務(wù)一的執(zhí)行绿店,并不需要等待任務(wù)二。所以等任務(wù)一結(jié)束庐橙,任務(wù)二也可以被執(zhí)行假勿。
當(dāng)然這里因?yàn)樘峤籅lock所在隊(duì)列,Block被執(zhí)行的隊(duì)列是完全不同的兩個(gè)隊(duì)列态鳖,所以這里用串行queue转培,也是不會(huì)死鎖的。大家可以自己寫個(gè)例子試試浆竭,這里就不贅述了浸须。
看到這我們可以知道一些sync的阻塞機(jī)制:
- sync提交Block,首先是阻塞的當(dāng)前提交Block的線程(簡(jiǎn)單理解下就是阻塞sync之后的代碼)邦泄。例如我們之前舉的例子中删窒,sync總是阻塞了任務(wù)二的執(zhí)行。
- 而在隊(duì)列中顺囊,輪到sync提交的Block肌索,僅僅阻塞串行queue,而不會(huì)阻塞并行queue特碳。(
dispatch_barrier_(a)sync
除外诚亚,我們后面會(huì)講到晕换。)
我們了解了sync的阻塞機(jī)制,再結(jié)合發(fā)生死鎖的根本原因來(lái)自于互相等待站宗,我們用下面一句話來(lái)總結(jié)一下闸准,會(huì)引起GCD死鎖的行為:
如果同步(sync)提交一個(gè)Block到一個(gè)串行隊(duì)列,而提交Block這個(gè)動(dòng)作所處的線程梢灭,也是在當(dāng)前隊(duì)列夷家,就會(huì)引起死鎖。
我相信或辖,如果看明白了上述所說(shuō)的瘾英,基本上可以放心的使用sync相關(guān)api,而不用去擔(dān)心死鎖的問(wèn)題颂暇。
關(guān)于更多死鎖例子這里就不寫了缺谴,基本上都是基于上述所說(shuō)的,只是在不同隊(duì)列中耳鸯,sync湿蛔,async組合形式不同,但是原理都是和上述一樣县爬。如果實(shí)在感興趣的阳啥,可以看看這篇文章:一篇專題讓你秒懂GCD死鎖問(wèn)題!
迷惑三:以下4個(gè)GCD方法的區(qū)別:
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
1)dispatch_async
這個(gè)就不用說(shuō)了,估計(jì)大家都用的非常熟悉财喳。
2)dispatch_barrier_async
察迟, 這個(gè)想必大家也知道是干嘛用的,如果不知道耳高,我也大概講講:
它的作用可以用一個(gè)詞概括--承上啟下扎瓶,它保證此前的任務(wù)都先于自己執(zhí)行,此后的任務(wù)也遲于自己執(zhí)行泌枪。當(dāng)然它的作用導(dǎo)致它只有在并行隊(duì)列中有意義概荷。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任務(wù)1
...
});
dispatch_async(queue, ^{
// 任務(wù)2
...
});
dispatch_async(queue, ^{
// 任務(wù)3
...
});
dispatch_barrier_async(queue, ^{
// 任務(wù)4
...
});
dispatch_async(queue, ^{
// 任務(wù)5
...
});
dispatch_async(queue, ^{
// 任務(wù)6
...
});
例如上述任務(wù),任務(wù)1碌燕,2误证,3的順序不一定,4在中間修壕,最后是5愈捅,6任務(wù)順序不一定。它就像一個(gè)柵欄一樣慈鸠,擋在了一個(gè)并行隊(duì)列中間改鲫。
當(dāng)然這里有一點(diǎn)需要注意的是:dispatch_barrier_(a)sync只在自己創(chuàng)建的并發(fā)隊(duì)列上有效,在全局(Global)并發(fā)隊(duì)列、串行隊(duì)列上像棘,效果跟dispatch_(a)sync效果一樣稽亏。
我們講到這,順便來(lái)講講它的用途缕题,例如我們?cè)谝粋€(gè)讀寫操作中:
我們要知道一個(gè)數(shù)據(jù)截歉,讀與讀之間是可以用線程并行的,但是寫與寫烟零、寫與讀之間瘪松,就必須串行同步或者使用線程鎖來(lái)保證線程安全。但是我們有了dispatch_barrier_async
锨阿,我們就可以如下使用:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//讀操作
});
dispatch_async(queue, ^{
// 讀操作
});
dispatch_barrier_async(queue, ^{
// 寫操作
});
dispatch_barrier_async(queue, ^{
// 寫操作
});
dispatch_async(queue, ^{
// 讀操作
});
這樣寫操作的時(shí)候宵睦,始終只有它這一條線程在進(jìn)行。而讀操作一直是并行的墅诡。這么做充分利用了多線程的優(yōu)勢(shì)壳嚎,還不需要加鎖,減少了相當(dāng)一部分的性能開銷末早。實(shí)現(xiàn)了讀寫操作的線程安全烟馅。
3)dispatch_barrier_sync
這個(gè)方法和dispatch_barrier_async
作用幾乎一樣,都可以在并行queue中當(dāng)做柵欄然磷。
唯一的區(qū)別就是:dispatch_barrier_sync
有GCD的sync共有特性郑趁,會(huì)阻塞提交Block的當(dāng)前線程,而dispatch_barrier_async
是異步提交姿搜,不會(huì)阻塞寡润。
4)dispatch_sync
,我們來(lái)講講它和dispatch_barrier_sync
的區(qū)別舅柜。二者因?yàn)槭莝ync提交悦穿,所以都是阻塞當(dāng)前提交Block線程。
而它倆唯一的區(qū)別是:dispatch_sync
并不能阻塞并行隊(duì)列业踢。其實(shí)之前死鎖有提及過(guò),擔(dān)心大家感覺(jué)疑惑礁扮,還是寫個(gè)例子:
dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
dispatch_async(queue, ^{
NSLog(@"任務(wù)二");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三");
});
//睡眠2秒
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)一");
});
輸出結(jié)果 :
任務(wù)三
任務(wù)二
任務(wù)一
很顯然知举,并行隊(duì)列沒(méi)有被sync所阻塞。
而dispatch_barrier_sync
可以阻塞并行隊(duì)列(柵欄作用的體現(xiàn)):
dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_sync(queue, ^{
dispatch_async(queue, ^{
NSLog(@"任務(wù)二");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三");
});
//睡眠2秒
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)一");
});
輸出結(jié)果 :
任務(wù)一
任務(wù)二
任務(wù)三
總結(jié)一下:
這些API都是有各自應(yīng)用場(chǎng)景的太伊,蘋果也不會(huì)給我們提供重復(fù)而且毫無(wú)意義的方法雇锡。
其中在AF的圖片緩存處理中,就有大量組合的用到:
dispatch_barrier_sync
僚焦、dispatch_barrier_async
锰提、dispatch_sync
這些API,主要是為了保證在不使用鎖下,緩存數(shù)據(jù)的讀寫的線程安全立肘。感興趣的可以去樓主之前的文章中看看:
AFNetworking之UIKit擴(kuò)展與緩存實(shí)現(xiàn)
大概就寫到這里了边坤,如果小伙伴有其它感到迷惑的問(wèn)題,可以評(píng)論谅年,樓主會(huì)一一回復(fù)茧痒,如果這個(gè)問(wèn)題問(wèn)的多的話,會(huì)繼續(xù)補(bǔ)充在本文中融蹂。