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