iOS多線程之二(GCD)

GCD的基本使用

一、主隊列介紹

主隊列:是和主線程相關(guān)聯(lián)的隊列,主隊列是GCD自帶的一種特殊的串行隊列灿意,放在主隊列中得任務(wù),都會放到主線程中執(zhí)行缤剧。

提示:如果把任務(wù)放到主隊列中進(jìn)行處理,那么不論處理函數(shù)是異步的還是同步的都不會開啟新的線程荒辕。

獲取主隊列的方式:

dispatch_queue_t?queue=dispatch_get_main_queue();

(1)使用異步函數(shù)執(zhí)行主隊列中得任務(wù)汗销,代碼示例:


執(zhí)行效果:

(2)使用同步函數(shù)抵窒,在主線程中執(zhí)行主隊列中得任務(wù),會發(fā)生死循環(huán)李皇,任務(wù)無法往下執(zhí)行削茁。示意圖如下:

二掉房、基本使用

1.問題

任務(wù)1和任務(wù)2是在主線程執(zhí)行還是子線程執(zhí)行,還是單獨(dú)再開啟一個新的線程卓囚?


打印結(jié)果:

2.開啟子線程,加載圖片



顯示效果:

打印結(jié)果:

要求使用GCD的方式哪亿,在子線程加載圖片完畢后,主線程拿到加載的image刷新UI界面蝇棉。



打印結(jié)果:

好處:子線程中得所有數(shù)據(jù)都可以直接拿到主線程中使用讨阻,更加的方便和直觀银萍。

三、線程間通信

從子線程回到主線程


GCD的常見用法

一贴唇、延遲執(zhí)行

1.介紹

iOS常見的延時執(zhí)行有2種方式

(1)調(diào)用NSObject的方法

[self?performSelector:@selector(run)?withObject:nil?afterDelay:2.0];

// 2秒后再調(diào)用self的run方法

(2)使用GCD函數(shù)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0?*?NSEC_PER_SEC)),?dispatch_get_main_queue(), ^{

// 2秒后異步執(zhí)行這里的代碼...

});

2.說明

第一種方法,該方法在那個線程調(diào)用戳气,那么run就在哪個線程執(zhí)行(當(dāng)前線程)链患,通常是主線程瓶您。

[self?performSelector:@selector(run)?withObject:nil?afterDelay:3.0];

說明:在3秒鐘之后纲仍,執(zhí)行run函數(shù)

代碼示例:



說明:如果把該方法放在異步函數(shù)中執(zhí)行,則方法不會被調(diào)用(BUG?)

第二種方法贸毕,

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0?*?NSEC_PER_SEC)),?dispatch_get_main_queue(), ^{

//延遲執(zhí)行的方法

});

說明:在5秒鐘之后,執(zhí)行block中的代碼段明棍。

參數(shù)說明:

什么時間乡革,執(zhí)行這個隊列中的這個任務(wù)摊腋。

代碼示例:


延遲執(zhí)行:不需要再寫方法,且它還傳遞了一個隊列兴蒸,我們可以指定并安排其線程。

如果隊列是主隊列橙凳,那么就在主線程執(zhí)行蕾殴,如果隊列是并發(fā)隊列岛啸,那么會新開啟一個線程区宇,在子線程中執(zhí)行值戳。

二炉爆、一次性代碼

1.實現(xiàn)一次性代碼

需求:點(diǎn)擊控制器只有第一次點(diǎn)擊的時候才打印。

實現(xiàn)代碼:



缺點(diǎn):這是一個對象方法芬首,如果又創(chuàng)建一個新的控制器赴捞,那么打印代碼又會執(zhí)行郁稍,因為每個新創(chuàng)建的控制器都有自己的布爾類型,且新創(chuàng)建的默認(rèn)為NO耀怜,因此不能保證改行代碼在整個程序中只打印一次恢着。

2.使用dispatch_once一次性代碼

使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次

static?dispatch_once_t?onceToken;

dispatch_once(&onceToken, ^{

//?只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)

});

整個程序運(yùn)行過程中财破,只會執(zhí)行一次掰派。

代碼示例:



效果(程序運(yùn)行過程中左痢,打印代碼只會執(zhí)行一次):

三系洛、隊列組

需求:從網(wǎng)絡(luò)上下載兩張圖片,把兩張圖片合并成一張最終顯示在view上略步。

1.第一種方法

代碼示例:



顯示效果:

打印查看:

問題:這種方式的效率不高,需要等到圖片1.圖片2都下載完成后才行趟薄。

提示:使用隊列組可以讓圖片1和圖片2的下載任務(wù)同時進(jìn)行,且當(dāng)兩個下載任務(wù)都完成的時候回到主線程進(jìn)行顯示竟趾。

2.使用隊列組解決

步驟:

創(chuàng)建一個組

開啟一個任務(wù)下載圖片1

開啟一個任務(wù)下載圖片2

同時執(zhí)行下載圖片1\下載圖片2操作

等group中的所有任務(wù)都執(zhí)行完畢, 再回到主線程執(zhí)行其他操作

代碼示例


打印查看(同時開啟了兩個子線程,分別下載圖片):

2.補(bǔ)充說明

有這么1種需求:

首先:分別異步執(zhí)行2個耗時的操作

其次:等2個異步操作都執(zhí)行完畢后岔帽,再回到主線程執(zhí)行操作

如果想要快速高效地實現(xiàn)上述需求玫鸟,可以考慮用隊列組

dispatch_group_t?group =??dispatch_group_create();

dispatch_group_async(group,?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0), ^{

//?執(zhí)行1個耗時的異步操作

});

dispatch_group_async(group,?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0), ^{

//?執(zhí)行1個耗時的異步操作

});

dispatch_group_notify(group,?dispatch_get_main_queue(), ^{

//?等前面的異步操作都執(zhí)行完畢后犀勒,回到主線程...

});

GCD中的死鎖

GCD中在主線程中用同步函數(shù)分派任務(wù)到串行隊列中會產(chǎn)生死鎖。

互相等待對方完成贾费,舉個簡單例子好了:

當(dāng)打印了1以后钦购,主線程調(diào)用dispatch_sync這個函數(shù),當(dāng)這個函數(shù)返回的時候主線程才能往下執(zhí)行押桃。但dispatch_sync返回的條件是里面的Block返回,里面的Block是不會執(zhí)行的导犹,因為它是被插到主隊列最后執(zhí)行,然而因為dispatch_sync無法返回谎痢,所以主隊列無法執(zhí)行到最后一個任務(wù)磕昼。

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

還不理解的話再來:

同樣用這里來講解吧节猿,dispatch_sync是調(diào)用它的線程上添加一個任務(wù)到指定隊列票从,正如這里所示,它接收了一個Block(任務(wù))并放到主隊列尾部滨嘱,即主線程所有的語句都執(zhí)行完畢后峰鄙,這個Block才會執(zhí)行太雨。

但如果隊列并不是主隊列,而是其他串行或并行躺彬,那么系統(tǒng)會創(chuàng)建一條運(yùn)行在主線程的隊列煤墙,這條隊列并不是主隊列,然后Block被加入的是隊列的頭也是尾仿野,所以先執(zhí)行并返回铣减,然后dispatch_sync返回。這時對于串行或并行隊列其實沒有區(qū)別葫哗,因為dispatch_sync總是在Block返回后才能繼續(xù)添加下一個任務(wù),不管串行并行球涛,最后都只能一個一個任務(wù)按順序執(zhí)行。

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

實在不理解繼續(xù)···徹底搞懂OC中GCD導(dǎo)致死鎖的原因和解決方案:

GCD提供了功能強(qiáng)大的任務(wù)和隊列控制功能亿扁,相比于NSOperationQueue更加底層捺典,因此如果不注意也會導(dǎo)致死鎖从祝。

所謂死鎖襟己,通常指有兩個線程A和B都卡住了牍陌,并等待對方完成某些操作。A不能完成是因為它在等待B完成毒涧。但B也不能完成贮预,因為它在等待A完成契讲。于是大家都完不成萌狂,就導(dǎo)致了死鎖(DeadLock)怀泊。

有一定GCD使用經(jīng)驗的新手通常認(rèn)為误趴,死鎖是很高端的操作系統(tǒng)層面的問題霹琼,離我很遠(yuǎn)凉当,一般不會遇上。其實這種想法是非常錯誤的看杭,因為只要簡單三行代碼(如果愿意忠藤,甚至寫在一行就可以)就可以人為創(chuàng)造出死鎖的情況楼雹。

intmain(intargc,constchar* argv[]) {??

@autoreleasepool

{

dispatch_sync(dispatch_get_main_queue(), ^(void){

NSLog(@"這里死鎖了");? ? ? ?

});??

}

return0;

}

比如這個最簡單的OC命令行程序就會導(dǎo)致死鎖模孩,運(yùn)行后不會看到任何結(jié)果。

在解釋為什么會死鎖之前榨咐,首先明確一下“同步&異步”“串行&并發(fā)”這兩組基本概念:

同步執(zhí)行:比如這里的dispatch_sync介却,這個函數(shù)會把一個block加入到指定的隊列中块茁,而且會一直等到執(zhí)行完blcok,這個函數(shù)才返回数焊。因此在block執(zhí)行完之前永淌,調(diào)用dispatch_sync方法的線程是阻塞的。

與之對應(yīng)的就有“異步執(zhí)行”的概念:

異步執(zhí)行:一般使用dispatch_async遂蛀,這個函數(shù)也會把一個block加入到指定的隊列中,但是和同步執(zhí)行不同的是蚕愤,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了。

接下來看一看另一組相對的概念:“串行&并發(fā)”

串行隊列:比如這里的dispatch_get_main_queue萍诱。這個隊列中所有任務(wù)悬嗓,一定按照先來后到的順序執(zhí)行裕坊。不僅如此包竹,還可以保

證在執(zhí)行某個任務(wù)時籍凝,在它前面進(jìn)入隊列的所有任務(wù)肯定執(zhí)行完了。對于每一個不同的串行隊列饵蒂,系統(tǒng)會為這個隊列建立唯一的線程來執(zhí)行代碼声诸。

與之相對的是并發(fā)隊列:

并發(fā)隊列:比如使用dispatch_get_global_queue退盯。這個隊列中的任務(wù)也是按照先來后到的順序開始執(zhí)行彼乌,注意是開始渊迁,但是它們的執(zhí)行結(jié)束時間是不確定的,取決于每個任務(wù)的耗時琉朽。對于n個并發(fā)隊列毒租,GCD不會創(chuàng)建對應(yīng)的n個線程而是進(jìn)行適當(dāng)?shù)膬?yōu)化

我們把整個dispatch_sync看作是一個任務(wù),比如說是非常關(guān)鍵墅垮、需要高度集中注意力的運(yùn)鈔過程惕医。這個過程非常重要,一旦開始執(zhí)行就必須一氣呵成曹锨,任何事情都不能干擾這個過程(阻塞線程)。

現(xiàn)在主線程開始執(zhí)行這個運(yùn)鈔任務(wù)剃允,任務(wù)執(zhí)行到一半時沛简,突然運(yùn)鈔員說我好累啊斥废,辛苦了好久了,我現(xiàn)在需要休息(向主線程添加了block)牡肉。運(yùn)鈔員天真的認(rèn)為捧灰,我知道運(yùn)鈔這個事很重要统锤,本來應(yīng)該等到運(yùn)鈔結(jié)束后再休息(這樣是串行)毛俏。但是在這之前饲窿,我的身體條件不允許工作。

但是之前已經(jīng)說了逾雄,運(yùn)鈔這件事很重要阀溶,它一旦開始就不能結(jié)束(阻塞線程)。怎么能允許有人中途休息呢银锻,因此要休息可以(block是可以執(zhí)行的),先把鈔票運(yùn)到安全地方再休息做鹰。

對應(yīng)到代碼里面來击纬,當(dāng)我們想要同步執(zhí)行這個block的時候钾麸,其實是告訴主線程,你把事情處理完了喂走,就過來處理我這個blcok,在此之前我一直等

你谋作。而主線程呢,剛處理dispatch_sync函數(shù)到一半呢遵蚜,這個函數(shù)還沒返回帖池,哪里有空去執(zhí)行block。因此這段代碼運(yùn)行后睡汹,并非卡在block

中無法返回肴甸,而是根本無法執(zhí)行到這個block囚巴。

好了原在,總結(jié)一下彤叉,到底什么是死鎖。首先秽浇,雖然剛剛我們提到了隊列和線程浮庐,以及它們之間的對應(yīng)關(guān)系,但是死鎖一定是針對線程而言的审残,隊列只是GCD給

出的抽象數(shù)據(jù)結(jié)構(gòu)。所謂的死鎖斑举,一定是發(fā)生在一個或多個線程之間的。那么死鎖和線程阻塞的關(guān)系呢懂昂,可以這么理解介时,雙向的阻塞導(dǎo)致了死鎖。因為阻塞是線程中

經(jīng)常發(fā)生的事情沸柔,最多就是主線程的阻塞影響了用戶體驗。而一旦出現(xiàn)了雙向的阻塞铲敛,就導(dǎo)致了死鎖。我們可以看到伐蒋,主線程是串行的工三,在執(zhí)行某一個任務(wù)的時候線

程被阻塞了,而這個任務(wù)(dispatch_sync)在執(zhí)行時俭正,又要求阻塞主線程,從而導(dǎo)致了互相的阻塞焙畔,也就是死鎖。

接下來我們思考一下,什么情況下會導(dǎo)致死鎖儿惫。這個問題可能一下子難以得出準(zhǔn)確的回答澡罚,為了解決這個問題肾请,我打算使用排除法。即先看看什么情況下不會發(fā)生死鎖铛铁。比如說隔显,異步執(zhí)行block肯定不會發(fā)生死鎖。比如剛剛的代碼改成這樣:

dispatch_async(dispatch_get_global_queue(0,0), ^(void){

NSLog(@"這就不死鎖了");

});

甚至可以總結(jié)出來:異步執(zhí)行一定不會導(dǎo)致死鎖荣月。因為回顧一下之前導(dǎo)致的死鎖的原因,很重要的一點(diǎn)是主線程在執(zhí)行dispatch_sync梳毙,這是個

同步方法哺窄,block執(zhí)行完之前都不會返回账锹。而既然是異步的執(zhí)行,那么是立刻返回的奸柬,因此不會阻塞主線程生年。雙向的阻塞不成立了廓奕,只是主線程處理blcok

時阻塞抱婉,但這不會引起死鎖桌粉。

根據(jù)之前我們的分析和總結(jié),GCD中我們需要關(guān)心的就是同步還是異步執(zhí)行铃肯,以及把block添加到哪個隊列中(串行還是并發(fā))患亿。

所以接下來就只需要重點(diǎn)思考一下,在同步執(zhí)行時步藕,什么時候會導(dǎo)致死鎖√舾瘢可以再得出一個結(jié)論,向并發(fā)隊列中添加block不會導(dǎo)致死鎖漂彤。再次回顧一下

之前導(dǎo)致的死鎖的原因雾消,由于在串行隊列中添加了block瞬逊,block一直等到前面的任務(wù)處理完才會執(zhí)行仪或,從而導(dǎo)致了死鎖∈恐瑁現(xiàn)在即使是同步的向并發(fā)隊列中

添加block范删,GCD會自動為我們管理線程拷肌,主線程目前阻塞著(處理這個同步方法),那就新建一個新的線程巨缘,但無論如何這個被添加block遲早都會被

執(zhí)行添忘。而所有添加的block被執(zhí)行完后,同步方法也就返回了搁骑。因此不會導(dǎo)致死鎖。

最后再來討論一下用同步方法向串行隊列添加block的情況又固,這種情況下會不會造成死鎖呢,答案是不一定仰冠。事實上乏冀,導(dǎo)致死鎖的原因一定是:

在某一個串行隊列中,同步的向這個隊列添加block辆沦。

比如文章開頭的例子就屬于這種情況。如果同步的向另外一個串行隊列添加方法识虚,并不一定導(dǎo)致死鎖。比如:

dispatch_queue_tqueue = dispatch_queue_create("serial",nil);dispatch_sync(queue, ^(void){

NSLog(@"這個也不會死鎖");

});

分析一下代碼舷礼,向名為serial的串行隊列添加任務(wù)后鹃彻,GCD自動創(chuàng)建了一個新的線程,在這個線程中執(zhí)行block方法蛛株。在這個過程中,主線程和新的線程都是阻塞的育拨,但是并不會導(dǎo)致死鎖。

為什么說向另一個串行隊列添加任務(wù)不一定導(dǎo)致死鎖呢熬丧,因為隊列是可以嵌套的笋粟,比如在A隊列(串行)添加一個任務(wù)a,在a這個任務(wù)中向B隊列(串行)

添加任務(wù)b害捕,在b這個任務(wù)中又向A隊列添加任務(wù)绿淋,這就間接滿足了“在某一個串行隊列中尝盼,同步的向這個隊列添加block”。但是我們好像每一次都沒有直接

向相同的隊列中添加block盾沫。

所以判斷是否發(fā)生死鎖的最好方法就是看有沒有在串行隊列(當(dāng)然也包括主隊列)中向這個隊列添加任務(wù)裁赠。又因為我們知道每個串行隊列對應(yīng)一個線程,所以只要不在某個線程中調(diào)用會阻塞這個線程的方法即可佩捞。

事實上,我們使用同步的方法編程蕾哟,往往是要求保證任務(wù)之間的執(zhí)行順序是完全確定的一忱。且不說GCD提供了很多強(qiáng)大的功能來滿足這個需求谭确,向串行隊列中

同步的添加任務(wù)本身就是不合理的,畢竟隊列已經(jīng)是串行的了琼富,直接異步添加就可以了啊仪吧。所以鞠眉,解決文章開頭那個死鎖例子的最簡單的方法就是在合適的位置添加

一個字母a薯鼠。

參考博客網(wǎng)站:

http://www.cnblogs.com/wendingding/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AF%87/

http://www.myexception.cn/mobile/2001282.html

http://www.cocoachina.com/bbs/read.php?tid-1482884-page-1.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末械蹋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子哗戈,更是在濱河造成了極大的恐慌郊艘,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纱注,死亡現(xiàn)場離奇詭異,居然都是意外死亡胆胰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蜀涨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞎嬉,“玉大人,你說我怎么就攤上這事氧枣°灞” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵痒筒,是天一觀的道長。 經(jīng)常有香客問我茬贵,道長移袍,這世上最難降的妖魔是什么解藻? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任葡盗,我火速辦了婚禮,結(jié)果婚禮上觅够,老公的妹妹穿的比我還像新娘胶背。我一直安慰自己,他們只是感情好钳吟,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窘拯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涤姊。 梳的紋絲不亂的頭發(fā)上暇番,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音壁酬,去河邊找鬼。 笑死恨课,一個胖子當(dāng)著我的面吹牛舆乔,可吹牛的內(nèi)容都是我干的剂公。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼诬留,長吁一口氣:“原來是場噩夢啊……” “哼斜纪!你這毒婦竟也來了贫母?” 一聲冷哼從身側(cè)響起盒刚,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎因块,沒想到半個月后橘原,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趾断,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吩愧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡雁佳,死狀恐怖脐帝,靈堂內(nèi)的尸體忽然破棺而出糖权,到底是詐尸還是另有隱情堵腹,我是刑警寧澤星澳,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站禁偎,受9級特大地震影響荡含,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜届垫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望装处。 院中可真熱鬧误债,春花似錦妄迁、人聲如沸寝蹈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黔州。三九已至耍鬓,卻和暖如春阔籽,著一層夾襖步出監(jiān)牢的瞬間牲蜀,已是汗流浹背笆制。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工涣达, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人度苔。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓匆篓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸦概。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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