GCD是蘋果為多核并行運算提出的方案,可以更高效的利用CPU。但是更重要的是它使多任務處理更加高效。因為它會自動合理的運用多核CPU。并且自從GCD的內存管理也加入ARC之后,它就能自動管理線程的生命周期。我們只需要把需要的操作(block)告訴它就可以了。
凡說GCD,肯定要說兩個概念
任務和隊列,任務可以同步執行和異步執行,存放任務的隊列分為串行隊列和并行隊列。
一.任務 (block)
就是咱們要執行的操作,GCD中就是block,任務執行在哪個線程由執行方式來決定
1.同步執行(sync)
會阻塞當前任務所在的線程(同步線程),dispatch_sync(queue, ^(block))把block放到queue中在當前線程執行。
2.異步執行(async)
不會阻塞當前任務所在的線程(異步線程),dispatch_async(queue, ^(block)),如果是串行隊列,則只開一個線程,如果是并行隊列,則開多個線程。
所以說sync和async決定block在哪個線程中執行
二.隊列 (queue)
存放任務的地方,負責調度任務(block)
1.串行隊列(SERIAL)顧名思義就是按順序執行隊列中的任務,一個任務完成再執行下一個任務。
主隊列:dispatch_get_main_queue 是一個特殊的串行隊列,運行在主線程中,UI相關操作都要在該隊列中執行。
自定義串行隊列:dispatch_queue_create("標識", DISPATCH_QUEUE_SERIAL),最后一個參數可以為NULL,默認創建的是串行隊列
2.并行隊列(CONCURRENT)就是很多任務并發執行,其實GCD中的并行隊列也是根據FIFO的原則取出任務,不同的是取出任務后GCD會新開一個線程來執行任務。
全局隊列:dispatch_get_global_queue(優先級,0); 蘋果公開的全局并行隊列,一共有四個優先級
自定義并行隊列:dispatch_queue_create("標識", DISPATCH_QUEUE_CONCURRENT)
三.隊列和任務是怎么執行的
GCD的基本概念雖然不多,但是用起來還是需要理解的深刻一些,比如任務在不同的隊列類別里用不同的方式執行會造成什么樣的結果,接下來咱們就來一項一項說。
1.串行隊列同步執行?
2.串行隊列異步執行
執行結果:
1-4為串行隊列同步執行
5-6為串行隊列異步執行
可以看出來:1-4在當前線程一個一個執行,5-6新開了一個線程一個一個執行(如果串行隊列為主隊列,則在主線程中執行)
3.并行隊列同步執行 4.并行隊列異步執行
執行結果:
1-4為并行隊列同步執行
5-6為并行隊列異步執行
可以看出來:1-4在當前線程一個一個執行,5-6新開了多個線程并發執行
四.實戰場景
理解了任務、隊列的運行規則,下面就來看看實際項目中在何種場景下運用GCD
1.運用dispatch_async來避免一些耗時的任務阻塞主線程(卡死界面).
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? NSLog(@"耗時的操作讀取數據庫,網絡操作等");
? ? ? ? dispatch_sync(dispatch_get_main_queue(), ^{
? ? ? ? ? ? ? NSLog(@"操作完成_刷新UI在:%@",i,[NSThread currentThread]);
? ? ? ? });
});
這里可能有人會問:回到主線程的操作,dispatch_sync和dispatch_async有什么區別?
大家可以想想這個問題,如果理解了前面說的隊列和任務,你應該知道結果,文章后面還會提到。
2.運用dispatch_apply進行快速迭代.
如果是串行隊列,dispatch_apply和for是一樣的,
如果運用在并發隊列,那么dispatch_apply就會并發的執行任務(block),大大的提高遍歷速度。
> 兩種方法各循環100000次,for耗時32.9秒,apply耗時19.4秒
> 效率提高了40%多。
有人說可以在for循環里面用dispatch_async開啟子線程執行任務,的確可以,但是如果開啟的子線程多了,很有可能線程爆炸造成死鎖等情況。而GCD會管理并發,所以apply還能避免線程爆炸的問題,實在是居家旅行、殺人滅口,必備良藥。
3.dispatch_group 調度組
開發中我們經常會遇到這樣的需求:同時調用多個接口,所有接口返回后再刷新界面。如果不用調度組的話,應該怎么做?大部分人都會把這幾個接口串行起來,等最后一個接口返回再執行刷新界面的方法,這樣做的話如果其中一個接口返回失敗,那么整個頁面就無法刷新了。還有的同學每個接口回來都刷新一次界面,這樣會造成頁面閃爍,嚴重的話,并發調用同時回來結果操作同一數據源,還有可能造成崩潰。
這時無疑用GCD調度組是最好的解決方法。調度組會在組里所有的任務執行完畢后發送一個通知告訴我們,組內的任務全部執行完畢。
接收通知的方式有兩種,
同步執行的dispatch_block_wait,會阻塞當前線程并等待之前的任務全部執行完或超時再執行wait中隊列里的任務
異步執行的dispatch_group_notify,作用和wait一樣,但是是異步執行的,所以不會阻塞當前線程
dispatch_group_enter和dispatch_group_leave可以手動管理group中的任務計數,enter為+1,leave為-1,當計數為0時,才會進入wait或notify中的任務
具體執行看代碼:這里用after延遲提交任務(block)的做法來模擬調用接口時的情景。
以上代碼中創建了一個調度組group,并且指定調度組中的任務在全局隊列中運行。在執行結果中可以看到,全局隊列中先執行了任務2和任務1(強制停止了1秒),因為wait會在當前線程等待任務1,2都完成之后才執行,所以wait執行完后再執行任務3,4,這里用了手動計數的方法控制任務計數,當3,4都執行完后,計數歸0,最后計入notify的任務,當我們需要同時并發執行多個接口之后再執行某項操作時,調度組非常實用。
4.GCD中容易遇到的死鎖問題
GCD的任務和隊列都是在線程中運行的,所以頻繁的操作線程如果不注意會很容易造成死鎖問題,所謂死鎖,就是指兩個線程互相等待對方完成某項操作,導致線程卡死,當然歸根結底是對任務和隊列的運行方式理解的還不夠徹底,下面列舉一些容易造成死鎖的現象,coding中要格外注意。
案例1:最簡單的死鎖現象
控制臺:
1.
我們來分析一下堆棧信息,根據FIFO的原則,任務1 - 同步線程-任務3-任務2
因為同步線程阻塞了主線程,所以任務2等待任務3執行,任務3又等待任務2,造成死鎖,程序卡死。
這時如果我們再稍微復雜一些呢
案例2:串行隊列中同步執行一個并行隊列中的任務
控制臺:
1
2
3
這個比較好理解,在主線程中打印1,這時同步線程在全局隊列里面執行2,不會像1一樣把任務2直接加在主隊列隊尾,所以不存在2,3相互等待的情況,而是同步線程阻塞了主線程后等待任務2執行完畢后,順序執行3.
案例3:異步線程執行后回到同步線程執行
是不是很眼熟,這就是4.實戰場景中的第一個案例,我們當然不是要演示這個,繼續看下圖兩張圖
控制臺:
1
8
2
3
4
5
6
7
控制臺:
1
8
2
7
3
4
5
6
控制臺:
分析:輸出0后,就是異步線程,所以任務4不用等待,1,4執行順序不一定。任務4完成后,接著是一個阻塞線程的同步任務5,但是加入到全局隊列的異步線程不受影響,繼續執行1后面的同步線程中的任務2,并且任務3需要等任務2完成后才能執行。但是這時主線程已經被一個同步線程的任務5,和6死鎖,所以任務2也無法執行。
總結
GCD除了上面說的這些,只要理解了他的運行方式,可以靈活的組合出很多用法,當然這篇文章只是說了GCD的鳳毛麟角,NSOperation和NSOperationQueue還沒有提到,先理解了GCD,咱們下次說NSOperation和NsOperationQueue。