iOS學(xué)習(xí)筆記系列 - Grand Central Dispatch

筆者本科剛畢業(yè),做iOS也有半年了,一直覺得純工作不總結(jié)沒什么沉淀。看到簡(jiǎn)書那么多大神在做學(xué)習(xí)筆記,于是決定也跟著做點(diǎn),希望對(duì)自己有幫助。如果能順道幫到一兩個(gè)同道,那將更是完美了。

今天的話題是GCD。筆者由于缺乏實(shí)戰(zhàn)經(jīng)驗(yàn),總覺得多線程什么的處理起來非常復(fù)雜,于是決定花點(diǎn)時(shí)間把GCD搞懂。參考上主要看了dullglass大神的總結(jié),獲益匪淺,特此鳴謝。下面是這篇文章的總綱:

  • GCD存在的意義是什么
  • GCD隊(duì)列
  • GCD分發(fā)模式
  • 實(shí)戰(zhàn)

為什么需要知道GCD

GCD就是為了簡(jiǎn)化對(duì)多線程的管理而存在的。有了GCD的存在,我們就不再需要處理繁瑣又容易出錯(cuò)的線程管理。GCD本身管理著一個(gè)線程池,而把線程的概念抽象成了隊(duì)列(所以線程 != 隊(duì)列)。系統(tǒng)會(huì)根據(jù)現(xiàn)在的狀態(tài)分配線程池里的線程去執(zhí)行一個(gè)或多個(gè)隊(duì)列里的任務(wù)(注意一個(gè)線程可能同時(shí)在執(zhí)行多個(gè)隊(duì)列里的任務(wù)的情況)。有了GCD以后,程序員要做的就只是告訴GCD你要做什么,分配到哪個(gè)隊(duì)列上去執(zhí)行,剩下的事情就可以交給GCD了。那么問題來了,什么是隊(duì)列?GCD是怎么通過隊(duì)列的概念把這些繁瑣的細(xì)節(jié)抽象出來的?

廢話不說了,直接上圖。

GCD 隊(duì)列

GCD隊(duì)列和數(shù)據(jù)結(jié)構(gòu)的隊(duì)列概念上是一樣的,用以管理待執(zhí)行的任務(wù)。如上圖所示,iOS系統(tǒng)默認(rèn)已經(jīng)有了5個(gè)隊(duì)列,系統(tǒng)相關(guān)任務(wù)就是在這五個(gè)隊(duì)列上執(zhí)行的。所以要注意,寫代碼時(shí)如果用到這五個(gè)隊(duì)列,一定不能假定隊(duì)列上只有你自己添加的任務(wù)(因?yàn)橄到y(tǒng)也會(huì)往里面添加)。

GCD的隊(duì)列按其性質(zhì)可分為三種:

  • 線性隊(duì)列 (Serial Dispatch Queue)
  • 并發(fā)隊(duì)列 (Concurrent Dispatch Queue)
  • 主隊(duì)列 (Main Dispatch Queue)
線性隊(duì)列

線性隊(duì)列上的任務(wù)按照FIFO的原則被依次執(zhí)行。在徹底執(zhí)行完隊(duì)列最前面的任務(wù)前,隊(duì)列的其他任務(wù)可以保證不被開始執(zhí)行。這也是線性隊(duì)列存在的意義所在:它能保證任務(wù)執(zhí)行的順序,從而在某種程度上解決了線程安全的問題(雖然線性的方式效率比較低)。比如說,當(dāng)你需要控制對(duì)某個(gè)資源的讀寫,保證任何時(shí)刻最多只能有一個(gè)線程在對(duì)其進(jìn)行更改時(shí),就可以采用線性隊(duì)列,把所有讀寫的操作都放到同一個(gè)線性隊(duì)列上執(zhí)行。線性隊(duì)列的實(shí)際應(yīng)用大家可以參考筆者寫的關(guān)于CocoaLumberjack的一篇文章。

關(guān)于線性隊(duì)列還有很重要的一點(diǎn),就是雖然隊(duì)列里的任務(wù)執(zhí)行的順序是依次的,但是執(zhí)行每個(gè)任務(wù)的線程卻不一定是同一個(gè)?。。ㄟ@點(diǎn)知道一下就好了,其實(shí)對(duì)于具體的代碼實(shí)現(xiàn)也不會(huì)有什么影響,畢竟GCD存在的意義就是讓我們不用考慮線程的問題。)

并發(fā)隊(duì)列

和線性隊(duì)列的區(qū)別在于,并發(fā)隊(duì)列可以把手頭的任務(wù)分發(fā)給不同的線程同時(shí)執(zhí)行。也就是說,并發(fā)隊(duì)列并不會(huì)等待上一個(gè)任務(wù)完成了之后再分發(fā)下一個(gè)任務(wù)。即便如此,它還是保證了任務(wù)開始執(zhí)行的時(shí)間是FIFO的。并發(fā)隊(duì)列的主要用途就是處理一些計(jì)算量大的任務(wù),比如數(shù)據(jù)的傳輸?shù)鹊?,使其不至于阻塞?dāng)前隊(duì)列。系統(tǒng)默認(rèn)的全局隊(duì)列就是并發(fā)隊(duì)列。

主隊(duì)列

主隊(duì)列由系統(tǒng)創(chuàng)建,它的本質(zhì)也是一個(gè)線性隊(duì)列。主隊(duì)列上的所有任務(wù),一定會(huì)在主線程執(zhí)行(反之則不然,主線程有可能執(zhí)行其他隊(duì)列上的任務(wù))。由于主隊(duì)列主要負(fù)責(zé)系統(tǒng)Runloop的操作,阻塞主隊(duì)列(往主隊(duì)列上分發(fā)過多或者過于復(fù)雜的任務(wù))將會(huì)造成APP對(duì)用戶操作的反應(yīng)過慢,從而極大影響用戶對(duì)APP的體驗(yàn)。

好了,雖然話有點(diǎn)多,但至少我們把隊(duì)列的事情搞清楚了。接下來就是怎么分發(fā)任務(wù)到隊(duì)列了。

GCD分發(fā)模式

眾所周知,GCD分發(fā)模式有兩種:同步分發(fā)(dispatch_sync)和異步分發(fā)(dispatch_async)。這兩個(gè)的區(qū)別和用法其實(shí)非常簡(jiǎn)單,一句話就能概括:當(dāng)前代碼要不要等到分發(fā)的任務(wù)執(zhí)行完了再繼續(xù)執(zhí)行。如果要等,就用同步分發(fā),否則就用異步分發(fā)。

說實(shí)話,筆者第一次看到這些的時(shí)候,最大的疑慮就是為什么會(huì)需要dispatch_sync,總覺得那不跟沒寫這個(gè)dispatch沒什么區(qū)別么,反正都是按順序執(zhí)行的。。。(畢竟當(dāng)時(shí)還是一臉懵逼,這些概念完全都沒搞懂。。。)

實(shí)戰(zhàn)

最后我們來舉幾個(gè)栗子,畢竟說了這么多沒幾個(gè)栗子太不像話了。下面的例子筆者不做分析,也建議讀者先不要看答案,自行分析,如果筆者所說的都理解透徹了,精確分析出答案應(yīng)該不費(fèi)吹灰之力。

以下例子假設(shè):

dispatch_queue_t serialQueue = 
dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);  
dispatch_queue_t concurrentQueue = 
dispatch_queue_create("serial", DISPATCH_QUEUE_CONCURRENT);  
示例1
NSLog(@"Start");
dispatch_sync(serialQueue, ^{
  [NSThread sleepForTimeInterval:3.0f];
  NSLog(@"Task 1");
});
dispatch_sync(serialQueue, ^{
  [NSThread sleepForTimeInterval:1.0f];
  NSLog(@"Task 2");
});
NSLog(@"End");

結(jié)果將是:

Start
Task 1
Task 2
End
示例2
NSLog(@"Start");
dispatch_async(serialQueue, ^{
  [NSThread sleepForTimeInterval:3.0f];
  NSLog(@"Task 1");
});
dispatch_async(serialQueue, ^{
  [NSThread sleepForTimeInterval:1.0f];
  NSLog(@"Task 2");
});
NSLog(@"End");

結(jié)果將是:

Start
End
Task 1
Task 2
示例3
NSLog(@"Start");
dispatch_sync(concurrentQueue, ^{
  [NSThread sleepForTimeInterval:3.0f];
  NSLog(@"Task 1");
});
dispatch_sync(concurrentQueue, ^{
  [NSThread sleepForTimeInterval:1.0f];
  NSLog(@"Task 2");
});
NSLog(@"End");

結(jié)果將是:

Start
Task 1
Task 2
End
示例4
NSLog(@"Start");
dispatch_async(concurrentQueue, ^{
  [NSThread sleepForTimeInterval:3.0f];
  NSLog(@"Task 1");
});
dispatch_async(concurrentQueue, ^{
  [NSThread sleepForTimeInterval:1.0f];
  NSLog(@"Task 2");
});
NSLog(@"End");

結(jié)果將是:

Start
End
Task 2
Task 1

寫在最后

GCD其實(shí)本身并不是很復(fù)雜,理解不是很難,但是要完全掌握得心應(yīng)手,還得靠實(shí)戰(zhàn)。筆者在這方面還是菜鳥一個(gè),只能說在理解上希望能幫到大家。文章有什么需要改正的歡迎大家指出改正,有什么問題也歡迎提出一起討論。

(BTW,這還是我在簡(jiǎn)書上的處女作,哈哈。。。希望能幫到有需要的人。)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容