筆者本科剛畢業(yè)蜡坊,做iOS也有半年了,一直覺得純工作不總結(jié)沒什么沉淀赎败★跹茫看到簡書那么多大神在做學習筆記,于是決定也跟著做點僵刮,希望對自己有幫助据忘。如果能順道幫到一兩個同道,那將更是完美了搞糕。
今天的話題是GCD勇吊。筆者由于缺乏實戰(zhàn)經(jīng)驗,總覺得多線程什么的處理起來非常復雜寞宫,于是決定花點時間把GCD搞懂萧福。參考上主要看了dullglass大神的總結(jié),獲益匪淺辈赋,特此鳴謝鲫忍。下面是這篇文章的總綱:
- GCD存在的意義是什么
- GCD隊列
- GCD分發(fā)模式
- 實戰(zhàn)
為什么需要知道GCD
GCD就是為了簡化對多線程的管理而存在的。有了GCD的存在钥屈,我們就不再需要處理繁瑣又容易出錯的線程管理悟民。GCD本身管理著一個線程池,而把線程的概念抽象成了隊列(所以線程 != 隊列)篷就。系統(tǒng)會根據(jù)現(xiàn)在的狀態(tài)分配線程池里的線程去執(zhí)行一個或多個隊列里的任務(wù)(注意一個線程可能同時在執(zhí)行多個隊列里的任務(wù)的情況)射亏。有了GCD以后,程序員要做的就只是告訴GCD你要做什么竭业,分配到哪個隊列上去執(zhí)行智润,剩下的事情就可以交給GCD了。那么問題來了未辆,什么是隊列窟绷?GCD是怎么通過隊列的概念把這些繁瑣的細節(jié)抽象出來的?
廢話不說了咐柜,直接上圖兼蜈。
GCD 隊列
GCD隊列和數(shù)據(jù)結(jié)構(gòu)的隊列概念上是一樣的,用以管理待執(zhí)行的任務(wù)拙友。如上圖所示为狸,iOS系統(tǒng)默認已經(jīng)有了5個隊列,系統(tǒng)相關(guān)任務(wù)就是在這五個隊列上執(zhí)行的遗契。所以要注意辐棒,寫代碼時如果用到這五個隊列,一定不能假定隊列上只有你自己添加的任務(wù)(因為系統(tǒng)也會往里面添加)牍蜂。
GCD的隊列按其性質(zhì)可分為三種:
- 線性隊列 (Serial Dispatch Queue)
- 并發(fā)隊列 (Concurrent Dispatch Queue)
- 主隊列 (Main Dispatch Queue)
線性隊列
線性隊列上的任務(wù)按照FIFO的原則被依次執(zhí)行涉瘾。在徹底執(zhí)行完隊列最前面的任務(wù)前,隊列的其他任務(wù)可以保證不被開始執(zhí)行捷兰。這也是線性隊列存在的意義所在:它能保證任務(wù)執(zhí)行的順序立叛,從而在某種程度上解決了線程安全的問題(雖然線性的方式效率比較低)。比如說贡茅,當你需要控制對某個資源的讀寫秘蛇,保證任何時刻最多只能有一個線程在對其進行更改時,就可以采用線性隊列顶考,把所有讀寫的操作都放到同一個線性隊列上執(zhí)行赁还。線性隊列的實際應(yīng)用大家可以參考筆者寫的關(guān)于CocoaLumberjack的一篇文章。
關(guān)于線性隊列還有很重要的一點驹沿,就是雖然隊列里的任務(wù)執(zhí)行的順序是依次的艘策,但是執(zhí)行每個任務(wù)的線程卻不一定是同一個!T尽(這點知道一下就好了朋蔫,其實對于具體的代碼實現(xiàn)也不會有什么影響罚渐,畢竟GCD存在的意義就是讓我們不用考慮線程的問題。)
并發(fā)隊列
和線性隊列的區(qū)別在于驯妄,并發(fā)隊列可以把手頭的任務(wù)分發(fā)給不同的線程同時執(zhí)行荷并。也就是說,并發(fā)隊列并不會等待上一個任務(wù)完成了之后再分發(fā)下一個任務(wù)青扔。即便如此源织,它還是保證了任務(wù)開始執(zhí)行的時間是FIFO的。并發(fā)隊列的主要用途就是處理一些計算量大的任務(wù)微猖,比如數(shù)據(jù)的傳輸?shù)鹊忍赶ⅲ蛊洳恢劣谧枞斍瓣犃小O到y(tǒng)默認的全局隊列就是并發(fā)隊列凛剥。
主隊列
主隊列由系統(tǒng)創(chuàng)建侠仇,它的本質(zhì)也是一個線性隊列。主隊列上的所有任務(wù)当悔,一定會在主線程執(zhí)行(反之則不然傅瞻,主線程有可能執(zhí)行其他隊列上的任務(wù))。由于主隊列主要負責系統(tǒng)Runloop的操作盲憎,阻塞主隊列(往主隊列上分發(fā)過多或者過于復雜的任務(wù))將會造成APP對用戶操作的反應(yīng)過慢嗅骄,從而極大影響用戶對APP的體驗。
好了饼疙,雖然話有點多溺森,但至少我們把隊列的事情搞清楚了。接下來就是怎么分發(fā)任務(wù)到隊列了窑眯。
GCD分發(fā)模式
眾所周知屏积,GCD分發(fā)模式有兩種:同步分發(fā)(dispatch_sync
)和異步分發(fā)(dispatch_async
)。這兩個的區(qū)別和用法其實非常簡單磅甩,一句話就能概括:當前代碼要不要等到分發(fā)的任務(wù)執(zhí)行完了再繼續(xù)執(zhí)行炊林。如果要等,就用同步分發(fā)卷要,否則就用異步分發(fā)渣聚。
說實話,筆者第一次看到這些的時候僧叉,最大的疑慮就是為什么會需要dispatch_sync
奕枝,總覺得那不跟沒寫這個dispatch沒什么區(qū)別么,反正都是按順序執(zhí)行的瓶堕。隘道。。(畢竟當時還是一臉懵逼,這些概念完全都沒搞懂谭梗。忘晤。。)
實戰(zhàn)
最后我們來舉幾個栗子默辨,畢竟說了這么多沒幾個栗子太不像話了德频。下面的例子筆者不做分析苍息,也建議讀者先不要看答案缩幸,自行分析,如果筆者所說的都理解透徹了竞思,精確分析出答案應(yīng)該不費吹灰之力表谊。
以下例子假設(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其實本身并不是很復雜,理解不是很難盖喷,但是要完全掌握得心應(yīng)手爆办,還得靠實戰(zhàn)。筆者在這方面還是菜鳥一個课梳,只能說在理解上希望能幫到大家距辆。文章有什么需要改正的歡迎大家指出改正,有什么問題也歡迎提出一起討論暮刃。
(BTW跨算,這還是我在簡書上的處女作,哈哈椭懊。诸蚕。。希望能幫到有需要的人氧猬。)