GCD容易讓人迷惑的幾個(gè)小問(wèn)題


寫在開頭:
本文旨在闡述一些大家容易產(chǎn)生迷惑的GCD相關(guān)內(nèi)容,如果是需要了解一些GCD概念或者基礎(chǔ)用法,可以看看這兩篇文章:GCD 掃盲篇巧談GCD
六敬。
目錄:
迷惑一:隊(duì)列和線程的關(guān)系
迷惑二:GCD的死鎖
迷惑三:以下這些API的異同與作用場(chǎng)景:
dispatch_asyncdispatch_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):

  1. 這里的syncasync并不局限于dispatch_sync稻励、dispatch_async父阻,而指的是GCD中所有同步異步的API。
  2. 這里我們用的是并行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ǔ)充在本文中融蹂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旺订,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子超燃,更是在濱河造成了極大的恐慌区拳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件意乓,死亡現(xiàn)場(chǎng)離奇詭異樱调,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)洽瞬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門本涕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人伙窃,你說(shuō)我怎么就攤上這事菩颖。” “怎么了为障?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵晦闰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鳍怨,道長(zhǎng)呻右,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任鞋喇,我火速辦了婚禮声滥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侦香。我一直安慰自己落塑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布罐韩。 她就那樣靜靜地躺著憾赁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪散吵。 梳的紋絲不亂的頭發(fā)上龙考,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天蟆肆,我揣著相機(jī)與錄音,去河邊找鬼晦款。 笑死炎功,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柬赐。 我是一名探鬼主播亡问,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肛宋!你這毒婦竟也來(lái)了州藕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤酝陈,失蹤者是張志新(化名)和其女友劉穎床玻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沉帮,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锈死,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了穆壕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片待牵。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖喇勋,靈堂內(nèi)的尸體忽然破棺而出缨该,到底是詐尸還是另有隱情,我是刑警寧澤川背,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布贰拿,位于F島的核電站,受9級(jí)特大地震影響熄云,放射性物質(zhì)發(fā)生泄漏膨更。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一缴允、第九天 我趴在偏房一處隱蔽的房頂上張望荚守。 院中可真熱鬧,春花似錦练般、人聲如沸矗漾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至晴及,卻和暖如春都办,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工琳钉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留势木,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓歌懒,卻偏偏與公主長(zhǎng)得像啦桌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子及皂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容