1. 創(chuàng)建隊列
//自己創(chuàng)建串行隊列
dispatch_queue_t dySerial = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);
//自己創(chuàng)建并行隊列
dispatch_queue_t dyConcurrent = dispatch_queue_create("并行隊列", DISPATCH_QUEUE_CONCURRENT);
//系統(tǒng)默認(rèn)主隊列诲祸,就是主線程
dispatch_queue_t osMainSerial = dispatch_get_main_queue();
//系統(tǒng)默認(rèn)的全局并行隊列
dispatch_queue_t osConcurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
2. 執(zhí)行列隊
//異步執(zhí)行? ? ? dispatch_async("傳入隊列", 任務(wù)block);
//同步執(zhí)行? ? ? ?dispatch_sync("傳入隊列", 任務(wù)block);
3. 不同執(zhí)行 + 不同隊列效果
3.1 同步執(zhí)行 + 并行隊列 不會創(chuàng)建多線程 順序執(zhí)行
3.2 異步執(zhí)行 + 并發(fā)隊列 會創(chuàng)建多個線程
3.3 同步執(zhí)行 + 串行隊列 不會創(chuàng)建新線程
3.4 異步執(zhí)行 + 串行隊列 只創(chuàng)建了一個線程(區(qū)別于當(dāng)前線程的新線程)
3.5 同步/異步 + 主隊列dispatch_get_main_queue()執(zhí)行效果
3.5.1? 特別注意: 同步執(zhí)行 + 對于特殊的串行隊列 dispatch_get_main_queue() 會出現(xiàn)死鎖
首先執(zhí)行任務(wù)1慌申,然后遇到dispatch_sync 同步線程出刷,當(dāng)前線程進入等待,等待同步線程中的任務(wù)2執(zhí)行完再執(zhí)行任務(wù)3皆的,這個任務(wù)2是加入到mainQueue主隊列中。
dispatch_sync(osMainSerial,^(void)()) 是同步一個任務(wù)到主隊列中蹋盆,而當(dāng)前去同步的正是主隊列费薄。因為隊列的執(zhí)行是FIFO(先進先出),所以兩個都在等待對方完成栖雾,就會造成死鎖楞抡。
3.5.2 如果這個操作放在非主隊列中執(zhí)行就不會有問題
3.6 異步執(zhí)行 + 對于特殊的串行隊列 dispatch_get_main_queue() 異步執(zhí)行的時候 不會創(chuàng)建新線程,只會使用當(dāng)前線程
4. 關(guān)于死鎖
4.1 在上3.5.1上介紹 同步執(zhí)行(dispatch_sync) + 主隊列 = 死鎖析藕。那么如果同步執(zhí)行的 + 非主隊列 是否就一定不會出現(xiàn)死鎖了召廷?看下面的代碼:
會發(fā)現(xiàn)還是會出現(xiàn)死鎖的情況,綜上所述不難發(fā)現(xiàn)這種死鎖產(chǎn)生的原因:
在串行隊列A(主隊列或自己創(chuàng)建的串行隊列)的線程中同步執(zhí)行一個任務(wù)噪径,而且這個任務(wù)還是放在串行隊列A中柱恤。這樣就會出現(xiàn)死鎖。
4.2 還有一種死鎖找爱,就是當(dāng)前的串行隊列被阻塞梗顺,然后同步任務(wù)到這個隊列就會出現(xiàn)
執(zhí)行順序:
main隊列中死循環(huán)不結(jié)束,任務(wù)3不執(zhí)行车摄,任務(wù)3又是global全局隊列同步過去的寺谤,global中同步的任務(wù)3不執(zhí)行完、任務(wù)4是不會執(zhí)行的吮播。
了解死鎖的發(fā)生時機后就很容易理解变屁,所謂隊列(串行并行)就是一個順序執(zhí)行的甬道舆乔。所謂同步異步就是決定執(zhí)行這個隊列的時候是否開辟新的線程去執(zhí)行来累。
關(guān)于GCD之間的通信沸停,就是一個概念記住就好当窗。不同的隊列直接相互通信这吻,只需要在執(zhí)行任務(wù)的時候同步或異步執(zhí)行任務(wù)的時候調(diào)用別的隊列傳值
5.? GCD常用方法
5.1 組操作dispatch_group
5.1.1? dispatch_group_notify宵膨、dispatch_group_wait 基本用法
異步執(zhí)行多個耗時任務(wù)运杭,然后多個任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)蛹头。這時候我們可以用到 GCD 的隊列組
dispatch_group_notify和dispatch_group_wait意思就是notify有block你可自己干某事,一個沒block 干某事只能接著wait后面寫順序執(zhí)行遮晚。
5.1.2? dispatch_group_enter性昭、dispatch_group_leave 基本用法
先看一個例子
發(fā)現(xiàn)doSomeThing1還沒執(zhí)行完就進入dispatch_group_notify打印刷新頁面,這是為什么了县遣?看代碼會發(fā)現(xiàn)doSomeThing1是放在了一個異步線程中執(zhí)行的糜颠。但是dispatch_group_notify只會監(jiān)聽dispatch_group_async中執(zhí)行的同步任務(wù)。如果dispatch_group_async執(zhí)行的是個異步任務(wù)萧求,那notify 監(jiān)聽結(jié)果可能不是你想要的其兴。
那我們能不能實現(xiàn)dispatch_group_async執(zhí)行多異步任務(wù),同時還能監(jiān)聽到任務(wù)全部完成了饭聚?做到這些就需要dispatch_group_enter和dispatch_group_leave了忌警,看下面例子:
dispatch_group_enter 用來標(biāo)記這個任務(wù)的開始,dispatch_group_leave用來標(biāo)記任務(wù)完成秒梳。這樣成對標(biāo)記可以實現(xiàn)標(biāo)記dispatch_group_async任務(wù)的真正執(zhí)行完畢,dispatch_group_notify也就能監(jiān)聽到了法绵。
記住:dispatch_group_enter 和 dispatch_group_leave 是成對出現(xiàn)的酪碘,不然dispatch_group 隊列會出錯朋譬。
5.1.3 用dispatch_group 處理多網(wǎng)絡(luò)請求都返回在刷新頁面
根據(jù)5.1.2 我們可以在實際開發(fā)中運用dispatch_group 來處理多個網(wǎng)絡(luò)請求都完成的通知。因為我們的網(wǎng)絡(luò)請求都是異步的兴垦,正好可以用dispatch_group_enter 和 dispatch_group_leave實現(xiàn)標(biāo)記完成
5.2 只執(zhí)行一次 dispatch_once
創(chuàng)建單例或者有整個程序運行過程中只執(zhí)行一次的代碼時使用徙赢。保證某段代碼在程序運行過程中只被執(zhí)行1次,并且即使在多線程的環(huán)境下探越,dispatch_once也可以保證線程安全狡赐。
5.3 延時執(zhí)行方法:dispatch_after
在指定時間(例如3秒)之后執(zhí)行某個任務(wù)。dispatch_after函數(shù)并不是在指定時間之后才開始執(zhí)行處理钦幔,而是在指定時間之后將任務(wù)追加到主隊列中枕屉。嚴(yán)格來說,這個時間并不是絕對準(zhǔn)確的鲤氢,但想要大致延遲執(zhí)行任務(wù)搀擂,dispatch_after函數(shù)是很有效的
5.4 柵欄方法 dispatch_barrier_async
我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后卷玉,才能開始執(zhí)行第二組操作哨颂。這樣我們就需要一個相當(dāng)于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來,當(dāng)然這里的操作組里可以包含一個或多個任務(wù)相种。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄威恼。
發(fā)現(xiàn):任務(wù)1和任務(wù)2 全部執(zhí)行完畢后 再執(zhí)行追加任務(wù),然后再執(zhí)行任務(wù)3和任務(wù)4,通過這個例子我們能明白dispatch_barrier_async主要是分割異步任務(wù)的作用沃测。
5.5? dispatch_apply 快速遍歷
和一般我們用for循環(huán)遍歷不同的是缭黔,如果dispatch_apply調(diào)用的時候傳入queue的是并發(fā)隊列食茎,那么它的遍歷就是異步執(zhí)行的蒂破,會在多個線程中進行,而我們直接for循環(huán)都是在當(dāng)前線程中進行的别渔。如下
發(fā)現(xiàn)打印的線程號不是按順序的附迷,但是如果傳入的是個串行隊列(非主隊列)它就和普通的for循環(huán)沒什么不同了。如下
發(fā)現(xiàn)輸出是按照順序的哎媚,和for循環(huán)沒什么不同喇伯,都是在主線程中進行的。
注意:如果執(zhí)行dispatch_apply的線程是串行隊列A的線程拨与,同時執(zhí)行dispatch_apply時傳入queue的也是串行隊列A稻据,會發(fā)生死鎖。比如直接在主隊列中執(zhí)行dispatch_apply买喧,同時傳入queue也是主隊列捻悯,如:
當(dāng)然這樣做也沒什么意義,一般dispatch_apply都是為了高效的異步的遍歷數(shù)組淤毛。所以一般dispatch_apply傳入的queue都是一個并發(fā)隊列今缚,同時還把dispatch_apply整體放入一個異步執(zhí)行的并發(fā)隊列中,如:
5.6 信號量 dispatch_semaphore
使用dispatch_semaphore 可以更好的控制并發(fā)多線程的任務(wù)處理低淡。
5.6.1 控制并發(fā)線程數(shù)量
現(xiàn)在有一組圖片地址姓言,需要開辟一個并發(fā)隊列來下載這些圖片,需要等這些圖片都下載完了才去做下面的事情
直接運行會發(fā)現(xiàn)會一下開辟imageArray.count個線程來處理任務(wù)蔗蹋。假如這里imageArray.count是100多何荚,很顯然,這樣的暴力處理是不切合實際的猪杭。使用Semaphore我們可以控制同時并發(fā)的線程數(shù)量餐塘。效果如下:
發(fā)現(xiàn)我們先開辟5個線程去下載,5個都下載完成了后再開辟5個繼續(xù)下載胁孙,這樣就可以減輕系統(tǒng)并發(fā)的數(shù)量唠倦。
看結(jié)果你是不是感覺這有點像dispatch_barrier_async都是把任務(wù)拆成幾部分一塊一塊執(zhí)行,但是他們是有本質(zhì)不同的:dispatch_barrier_async控制的是任務(wù)涮较,它操作的對象是任務(wù)分塊稠鼻,不會處理線程的多少,而Semaphore控制同時并發(fā)的線程數(shù)量狂票,再由一定的線程數(shù)量去執(zhí)行任務(wù)候齿。這好比同樣是蓋樓要10個人,barrier類似于我把樓分成2部分,我們10個人先蓋第一部分慌盯,第一部分完了再蓋第二部分周霉。而Semaphore是我們同時只讓5個人蓋樓,剩下的休息亚皂,誰干完了誰休息俱箱,之前休息的接著上來干。
那如果dispatch_semaphore_create(5)改成dispatch_semaphore_create(1)會發(fā)現(xiàn)雖然調(diào)用的是并發(fā)隊列灭必,但是限制信號量總數(shù)為1 后狞谱,并發(fā)隊列就變成了串行隊列
5.6.2 對多線程并發(fā)執(zhí)行任務(wù)共享資源進行? 加鎖處理
案例:總共有20張火車票,有兩個售賣火車票的窗口禁漓,一個是北京火車票售賣窗口跟衅,另一個是上海火車票售賣窗口播歼。兩個窗口同時售賣火車票伶跷,賣完為止。
這里的問題關(guān)鍵是秘狞,兩個窗口同時賣票叭莫,在賣出一張票后總票數(shù)都會減1,如何確保票不多賣或賣叉谒撼?這就相當(dāng)于計算機中的多條線程同時寫一個資源食寡,可能會出現(xiàn)混亂的問題
觀察發(fā)現(xiàn)余票在 11、6的時候出現(xiàn)混亂廓潜,多賣了票抵皱。這就是多線程訪問公共資源出現(xiàn)不同步的問題。我們使用dispatch_semaphore 來加鎖處理一下辩蛋,看看結(jié)果怎么樣呻畸?
發(fā)現(xiàn)總剩余票數(shù)沒有發(fā)生錯位。其實dispatch_semaphore這里就是控制了多線程處理任務(wù) 并發(fā)時機悼院,也是5.1.1的一種特殊形式伤为。
5.6.3 頁面有多個網(wǎng)絡(luò)請求都完成后才處理任務(wù)
上面說的使用dispatch_group_enter和dispatch_group_leave可以實現(xiàn)多網(wǎng)絡(luò)請求完成監(jiān)聽處理。我們使用dispatch_semaphore也可以完成這種操作
補充: 那么如何讓這些網(wǎng)絡(luò)請求有個先后順序了据途,就是說請求1返回回來的參數(shù)我要用到請求2上绞愚,這樣的需求也很常見,用到的知識就不是GCD的了颖医,而是NSOperation了
其中的[self request]里面代碼還是使用信號量來控制網(wǎng)絡(luò)請求真正結(jié)束
對于NSOperation實現(xiàn)多線程的方式有兩種:
1.將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中
2.將要執(zhí)行的任務(wù)添加到一個 NSOperationQueue 對象中
NSOperation 只是一個抽象類位衩,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)熔萧。分別是:NSInvocationOperation 和 NSBlockOperation 糖驴。創(chuàng)建一個 Operation 后僚祷,需要調(diào)用 start 方法來啟動任務(wù),它會默認(rèn)在當(dāng)前隊列同步執(zhí)行贮缕。你也可以調(diào)用 cancel 方法在中途取消一個任務(wù)辙谜。
總結(jié):NSOperation比GCD的好處
1、NSOperation能通過KVO鍵值觀察者來實時監(jiān)控operation的狀態(tài)(是否執(zhí)行感昼,是否取消)装哆, 而GCD無法通過KVO來實時監(jiān)控。通過completBlock來回調(diào)已經(jīng)執(zhí)行完畢抑诸。
2烂琴、NSOperation能通過設(shè)置依賴,使任務(wù)之間有先后順序蜕乡。GCD則可以通過柵欄函數(shù)來實現(xiàn)。
3梗夸、NSOperation能設(shè)置并行隊列中任務(wù)的優(yōu)先級(優(yōu)先級的作用是讓優(yōu)先級高的線程需要cpu時能夠更大幾率優(yōu)先得到cpu)反症,GCD是設(shè)置不同任務(wù)隊列(queue)的優(yōu)先級辛块,要實現(xiàn)block的優(yōu)先級,需要很多代碼铅碍。
4润绵、NSOperation是將任務(wù)放入隊列中來執(zhí)行的。GCD是將任務(wù)放入隊列中胞谈,通過同步異步函數(shù)來執(zhí)行(可以多個同步異步函數(shù))尘盼。
5、總體來說GCD更簡潔
補充:
1烦绳、怎么取消正在執(zhí)行的gcd任務(wù):
可以仿照operation的工作原理卿捎,設(shè)置一個BOOL變量,當(dāng)需要停止時設(shè)置成YES,執(zhí)行任務(wù)的時候去判斷這個狀態(tài)
2径密、多線程使用帶來的問題:資源競爭午阵、優(yōu)先倒置、死鎖
以上是根據(jù)別人的博客和自己代碼實踐所得享扔,如有冒犯和不足歡迎大家批評底桂。