本文來(lái)源是基于http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 烙荷。這篇文章是基于該博客寫的一個(gè)讀書筆記,是對(duì)原文章的知識(shí)點(diǎn)進(jìn)行重新梳理加工泰讽,加入我的個(gè)人理解,以及在工作中使用GCD的一些心得媚赖。本人能力有限,若是發(fā)現(xiàn)不足請(qǐng)?jiān)谠u(píng)論當(dāng)中指出,我會(huì)在文章中更新,不勝感激临庇!
GCD 特點(diǎn)?
1昵慌、GCD 通過(guò)將耗時(shí)任務(wù)放到后臺(tái)線程執(zhí)行來(lái)提交程序的響應(yīng)性
2、相對(duì)于直接使用線程和鎖的并發(fā)模型淮蜈,GCD 更易于使用斋攀,且能夠避免一些 bug
3、代碼執(zhí)行效率高
4梧田、所有相關(guān)的方法都是以dispatch命名開頭
并發(fā)的相關(guān)概念
1淳蔼、串行和并行
串行是同一個(gè)時(shí)間內(nèi)有且僅有一個(gè)任務(wù)在執(zhí)行(場(chǎng)景類似單人過(guò)一線天侧蘸,人可以比作任務(wù)),并行是同一個(gè)時(shí)間內(nèi)可以有多個(gè)任務(wù)在執(zhí)行(場(chǎng)景類似多人百米賽跑鹉梨,人可以比作任務(wù))讳癌。在GCD中,任務(wù)可以理解成一個(gè)Block存皂。
2晌坤、同步和異步
同步是提交任務(wù)之后會(huì)等待提交的任務(wù)執(zhí)行完成之后才做返回操作(場(chǎng)景類似監(jiān)考,監(jiān)考老師從考試開始時(shí)就一直呆在考室里面旦袋,直到考試結(jié)束后才走出考室)骤菠。異步就是提交任務(wù)之后,不管任務(wù)是否開始執(zhí)行疤孕,立即做返回操作(場(chǎng)景類似布置作業(yè)商乎,老師從上課開始來(lái)布置一次作業(yè),然后讓各位同學(xué)完成作業(yè)祭阀,布置完作業(yè)之后鹉戚,老師馬上就離開了)。
3专控、臨界值
臨界值可以理解成一塊不能并發(fā)執(zhí)行的代碼崩瓤,這塊代碼包含了一些共享的資源,不能允許多個(gè)線程同時(shí)訪問(wèn)(在幾個(gè)口渴的人面前踩官,只有一個(gè)水壺却桶,這幾個(gè)人只能一個(gè)一個(gè)直接用水壺喝水,水壺就是臨界值)蔗牡。
4颖系、競(jìng)爭(zhēng)條件
簡(jiǎn)而言之,多個(gè)線程在讀寫共享資源時(shí)辩越,執(zhí)行結(jié)果取決于線程的執(zhí)行時(shí)間和執(zhí)行順序(在幾個(gè)口渴的人面前嘁扼,只有一個(gè)水壺,這幾個(gè)人只能一個(gè)一個(gè)直接用水壺喝水黔攒,這幾個(gè)人當(dāng)中一個(gè)人能喝多少水取決于這個(gè)人是什么時(shí)候開始喝的水以及喝了多長(zhǎng)時(shí)間)趁啸。
5、死鎖
死鎖是線程中比較悲劇的一個(gè)問(wèn)題督惰,簡(jiǎn)單來(lái)說(shuō)就是不傅,線程A等著線程B執(zhí)行完成,線程B反過(guò)來(lái)等著線程A執(zhí)行完成赏胚,好了访娶,這樣線程AB就一直耗著了,這就是死鎖(用一個(gè)場(chǎng)景來(lái)描述這個(gè)死鎖觉阅,男生心里暗戀一個(gè)女生崖疤,碰巧該女生也暗戀這個(gè)男生秘车,2個(gè)人都在等著對(duì)方捅破最后的窗戶紙,最后2人就沒(méi)有浪漫的結(jié)果了劫哼,哈哈)叮趴。
6、線程安全
線程安全是一個(gè)比較常見(jiàn)的概念权烧,線程安全的代碼能夠在多線程和并發(fā)訪問(wèn)下不會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)誤等問(wèn)題眯亦,比如NSDictionary就是線程安全,它可以在同一時(shí)間內(nèi)被多個(gè)線程訪問(wèn)而不會(huì)有任何問(wèn)題豪嚎,對(duì)比之下NSMutableDictionary就是非線程安全的搔驼,在同一個(gè)時(shí)間內(nèi)只能有且只有一個(gè)線程操作它。(GCD的隊(duì)列就是線程安全的)
7侈询、上下文切換
上下文切換就是在一個(gè)進(jìn)程內(nèi)執(zhí)行多線程任務(wù)時(shí)候舌涨,線程之間切換時(shí)候保存和恢復(fù)線程的執(zhí)行狀態(tài),比如從主線程切換到其它線程扔字,這個(gè)時(shí)候系統(tǒng)會(huì)保存主線程的執(zhí)行狀態(tài)(包括不限于變量)囊嘉,從其它線程切換到主線程時(shí)候系統(tǒng)會(huì)恢復(fù)之前保存的主線程執(zhí)行狀態(tài)。
講完了并發(fā)的相關(guān)概念革为,接下來(lái)就開始講和GCD直接相關(guān)的技術(shù)了(在本文中任務(wù)與Block是等同的)扭粱。
隊(duì)列
GCD提供了隊(duì)列來(lái)處理任務(wù)(Block),隊(duì)列使用FIFO(先進(jìn)先出)的方法來(lái)管理你提交給GCD任務(wù)震檩,也就是說(shuō)在GCD的同一個(gè)隊(duì)列里面琢蛤,先提交的任務(wù)比慢提交的任務(wù)較先執(zhí)行,也就是先來(lái)先執(zhí)行抛虏。
1博其、串行隊(duì)列(Serial Queues)
顧名思義,串行隊(duì)列就是一個(gè)時(shí)間內(nèi)只執(zhí)行一個(gè)任務(wù)迂猴,只有前一個(gè)任務(wù)執(zhí)行完成之后慕淡,才會(huì)開始執(zhí)行下一個(gè)任務(wù),任務(wù)的被執(zhí)行順序和任務(wù)被提交到GCD的順序是一致的沸毁。示意圖如下(來(lái)自網(wǎng)絡(luò))
2峰髓、有串行隊(duì)列就會(huì)有并發(fā)隊(duì)列(Concuttent Queues)
并發(fā)隊(duì)列允許同一個(gè)時(shí)間內(nèi)可以有多個(gè)任務(wù)在執(zhí)行,不過(guò)和串行隊(duì)列一致的一點(diǎn)就是任務(wù)的被執(zhí)行順序和任務(wù)被提交到GCD的順序是一致的息尺。對(duì)于GCD來(lái)說(shuō)携兵,任務(wù)先來(lái)先執(zhí)行,不管你是什么類型的隊(duì)列掷倔。示意圖如下(來(lái)自網(wǎng)絡(luò))
既然知道了并發(fā)隊(duì)列和串行隊(duì)列眉孩,那你也應(yīng)該會(huì)猜到蘋果肯定也會(huì)給幾個(gè)默認(rèn)的隊(duì)列讓我們這些開發(fā)者使用的,接下來(lái)就來(lái)說(shuō)說(shuō)隊(duì)列的類型勒葱。系統(tǒng)提供了一個(gè)特殊的串行隊(duì)列給我們使用浪汪,這個(gè)隊(duì)列就是主線程隊(duì)列(main queue),它除了具備串行隊(duì)列的一個(gè)特點(diǎn)之外(一個(gè)時(shí)間內(nèi)只能執(zhí)行一個(gè)任務(wù)凛虽,只有前一個(gè)任務(wù)執(zhí)行完成之后死遭,才會(huì)開始執(zhí)行下一個(gè)任務(wù))還有一個(gè)大殺器,就是所有的任務(wù)保證都會(huì)在主線程中執(zhí)行凯旋,這意味著什么呢呀潭?意味著我們可以使用這個(gè)隊(duì)列來(lái)更新UI。(不管iOS還是Android系統(tǒng)至非,所有的UI操作都是要在主線程中來(lái)完成的)钠署。
說(shuō)完串行隊(duì)列說(shuō)說(shuō)系統(tǒng)提供的并發(fā)隊(duì)列吧!名字叫做全局分發(fā)隊(duì)列(Global Dispatch Queue)荒椭,它有四個(gè)不同優(yōu)先級(jí)別的并發(fā)隊(duì)列谐鼎,優(yōu)先級(jí)別分別是Background,low,default,high。使用這個(gè)全局分發(fā)隊(duì)列的時(shí)候并不是設(shè)置優(yōu)先級(jí)別越高越好趣惠,因?yàn)樘O果的Api也是使用這些隊(duì)列狸棍,所以在這些個(gè)隊(duì)列中已經(jīng)有存在一些任務(wù),通常的使用方法是設(shè)置默認(rèn)的優(yōu)先級(jí)別default味悄。
關(guān)鍵點(diǎn) dispatch_async草戈,dispath_sync,串行隊(duì)列侍瑟,并行隊(duì)列
講了這么多唐片,還沒(méi)有上代碼,好煩涨颜。我就圖方便,直接用他人的現(xiàn)有案例來(lái)說(shuō)吧费韭,http://cdn4.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_Start_1.zip ?(初始案例)
這是一個(gè)圖片案例,從網(wǎng)絡(luò)上加載圖片咐低,加載完成后點(diǎn)擊進(jìn)入查看大圖揽思,在大圖里面識(shí)別出人物的眼睛,并給眼睛貼上圖片见擦。
在開發(fā)中钉汗,為了有較好的用戶體驗(yàn),通常會(huì)把一些耗時(shí)的操作(文件操作鲤屡,數(shù)據(jù)庫(kù)操作损痰,網(wǎng)絡(luò)操作,圖片編解碼)放到后臺(tái)線程去操作酒来,以避免耗時(shí)操作阻塞主線程卢未。在被注釋掉的代碼中直接在UI主線程進(jìn)行圖片對(duì)象的處理(對(duì)人物的眼睛進(jìn)行識(shí)別然后貼圖),這個(gè)是一個(gè)耗時(shí)操作,所以對(duì)它進(jìn)行改進(jìn)辽社。
改進(jìn)之后伟墙,
1、將這個(gè)耗時(shí)操作放到后臺(tái)線程去操作滴铅,使用 dispath_async 異步提交任務(wù)到全局并發(fā)隊(duì)列(優(yōu)先級(jí)為高)戳葵,然后不管任務(wù)現(xiàn)在是否開始執(zhí)行,立即執(zhí)行下一個(gè)代碼NSLog打印內(nèi)容汉匙。
2拱烁、在第二個(gè)dispath_async中,此時(shí)上下文環(huán)境為后臺(tái)線程,那么要更新UI的話必須要在主線程內(nèi)更新噩翠,所以使用dispath_async異步提交更新UI的任務(wù)到主線程(dispatch_get_main_queue()獲取串行主線程隊(duì)列)戏自,dispath_async為異步執(zhí)行,不會(huì)等待任務(wù)執(zhí)行完返回伤锚,而是立即返回擅笔。
3、使用UIImage對(duì)象更新UI见芹。
從上面的代碼可以看到剂娄,dispath_async添加Block到一個(gè)隊(duì)列中然后馬上返回,這個(gè)Block在之后的某個(gè)時(shí)間點(diǎn)會(huì)被GCD執(zhí)行玄呛。那么什么時(shí)候使用dispath_async呢阅懦?網(wǎng)絡(luò)操作和CPU密集型的任務(wù)都可以放到后臺(tái)去執(zhí)行,這樣做的好處就是不會(huì)阻塞當(dāng)前主線程徘铝。
dispath_async和各個(gè)類型的隊(duì)列配合:
1耳胎、自定義串行隊(duì)列
如果你想要在后臺(tái)線程中連續(xù)執(zhí)行任務(wù),并且跟蹤任務(wù)的執(zhí)行惕它,那么dispath_async+自定義串行隊(duì)列是你的一個(gè)不錯(cuò)的選擇怕午。
2、主線程串行隊(duì)列
在后臺(tái)并發(fā)隊(duì)列執(zhí)行完任務(wù)之后,通常會(huì)跳到主線程更新UI淹魄。
3郁惜、后臺(tái)線程執(zhí)行耗時(shí)的非UI任務(wù)
dispath_sync和各個(gè)類型的隊(duì)列配合:
1、自定義串行隊(duì)列,在主線程內(nèi)同步執(zhí)行dispath_sync甲锡,將任務(wù)提交到自定義串行隊(duì)列執(zhí)行兆蕉。
2、主線程串行隊(duì)列缤沦,在主線程同步提交任務(wù)到主線程串行隊(duì)列會(huì)造成死鎖虎韵,死鎖,死鎖缸废。
代碼中的dispath_sync提交任務(wù)到主線程串行隊(duì)列之后包蓝,沒(méi)有馬上返回而是在等待任務(wù)執(zhí)行完成驶社。而在主線程串行隊(duì)列,已經(jīng)有dispath_sync這個(gè)方法在執(zhí)行了测萎,主線程隊(duì)列等dispath_sync這個(gè)方法執(zhí)行完成后亡电,才執(zhí)行dispath_sync的提交任務(wù)。dispath_sync提交的任務(wù)等待主線程绳泉,主線程等待dispath_sync方法逊抡,你等我姆泻,我等你零酪,大家一起Over!
3、并發(fā)隊(duì)列拇勃,dispath_sync和并發(fā)隊(duì)列一起使用是無(wú)法達(dá)到任務(wù)在后臺(tái)運(yùn)行的效果的四苇,因?yàn)椴l(fā)隊(duì)列的任務(wù)都是在主線程中,并且無(wú)法體現(xiàn)出并發(fā)隊(duì)列的并發(fā)特性
關(guān)鍵點(diǎn) dispatch_after
有些時(shí)候我們會(huì)有類似需求,比如在用戶登錄成功幾秒后開始從服務(wù)器同步數(shù)據(jù)到本地方咆≡乱福可以使用dispatch_after來(lái)完成類似延遲任務(wù)。在當(dāng)前案例中瓣赂,我們使用dispatch_after來(lái)延遲做出提示榆骚。
先創(chuàng)建一個(gè)dispatch_time_t 說(shuō)明從什么時(shí)候開始算起延遲多少時(shí)間,延遲時(shí)間的單位是毫秒煌集。在延遲時(shí)間到達(dá)之后dispatch_after提交任務(wù)給GCD妓肢。GCD根據(jù)情況安排任務(wù)的執(zhí)行。也就是說(shuō)這里的延遲是延遲任務(wù)的提交時(shí)間苫纤,而不是說(shuō)延遲多少時(shí)間后任務(wù)開始執(zhí)行碉钠。
dispatch_after和各個(gè)類型的隊(duì)列
1、自定義串行隊(duì)列(Custom Serial Queue),不建議在這個(gè)隊(duì)列中使用卷拘。
2喊废、主線程隊(duì)列(Main Queue)使用dispatch_after的好地方。
3栗弟、并發(fā)隊(duì)列(Concurrent Queue污筷,不建議在這個(gè)隊(duì)列中使用
關(guān)鍵點(diǎn) 線程安全的單例對(duì)象
在使用單例模式的時(shí)候,最重要的就是當(dāng)有多個(gè)線程同時(shí)訪問(wèn)單例對(duì)象的時(shí)候乍赫,單例對(duì)象需要時(shí)刻保持有且只有一個(gè)對(duì)象瓣蛀,數(shù)據(jù)讀寫線程安全。先看看案例
這個(gè)sharedManager方法如果有多個(gè)線程同時(shí)訪問(wèn)的話耿焊,那么就會(huì)出現(xiàn)創(chuàng)建多個(gè)sharedPhotoManager對(duì)象的問(wèn)題揪惦。啟用2個(gè)并發(fā)任務(wù)來(lái)訪問(wèn)sharedManager方法,
sharedManager方法做一些休眠操作和打印內(nèi)存地址操作罗侯,這樣的目的就是讓2個(gè)線程同時(shí)可以訪問(wèn)sharedManager方法
Xcode輸入的sharedPhotoManager對(duì)象內(nèi)存地址表明器腋,創(chuàng)建了3個(gè)sharedPhotoManager對(duì)象,那么這個(gè)作為單例肯定是很坑爹的。
關(guān)鍵點(diǎn) dispatch_once
dispatch_once()方法會(huì)以線程安全的方式有且僅有一次執(zhí)行Block里面的方法纫塌,這樣當(dāng)多個(gè)線程同時(shí)訪問(wèn)sharedManager方法的時(shí)候就不用擔(dān)心會(huì)出現(xiàn)多個(gè)對(duì)象被創(chuàng)建的情況了诊县,哈哈,簡(jiǎn)單吧措左!
說(shuō)完了初始化的問(wèn)題依痊,接下來(lái)就是getter和setter方法的問(wèn)題了。dispatch barriers是GCD提供的優(yōu)雅的讀寫鎖解決方案怎披。
看圖說(shuō)話胸嘁,在dispatch barriers 執(zhí)行前任務(wù)是并發(fā)執(zhí)行的,但是到了dispatch barriers后凉逛,并發(fā)執(zhí)行全部都變成了串行執(zhí)行性宏,當(dāng)dispatch barriers執(zhí)行完畢后,任務(wù)恢復(fù)成以前的狀態(tài)也就是并發(fā)執(zhí)行状飞。也就是說(shuō)在當(dāng)前的隊(duì)列中毫胜,當(dāng)任務(wù)執(zhí)行到dispatch barriers的Block的時(shí)候,dispatch barriers可以保證此時(shí)有且僅有這個(gè)Block在執(zhí)行诬辈,當(dāng)這個(gè)Block執(zhí)行成功后酵使,隊(duì)列恢復(fù)正常。(這個(gè)類似多人過(guò)獨(dú)木橋焙糟,有多個(gè)人并排走到一座獨(dú)木橋前口渔,這個(gè)獨(dú)木橋每次只能一個(gè)人通過(guò),且橋上一次只能站一個(gè)人酬荞,那么多個(gè)人就是單個(gè)單個(gè)的過(guò)獨(dú)立橋搓劫,過(guò)橋之后恢復(fù)過(guò)橋之前的狀態(tài))。
dispatch barriers和各個(gè)類型的隊(duì)列
1混巧、自定義串行隊(duì)列(Custom Serial Queue), 串行隊(duì)列本來(lái)就是一次只能執(zhí)行一個(gè)任務(wù)和dispatch barriers搭配沒(méi)有什么用處枪向。
2、并發(fā)隊(duì)列(Global Concurrent Queue),系統(tǒng)的API也有使用這些隊(duì)列咧党,為了不影響系統(tǒng)API執(zhí)行秘蛔,最好不要在這些隊(duì)列使用。
3傍衡、自定義并發(fā)隊(duì)列(Custom Concurrent Queue),墻裂推薦深员,需要保證setter和初始化方法線程安全的都是可以使用這個(gè)搭配。
解決getter和setter方法的線程安全問(wèn)題
dispatch_barrier_async在setter方法保證往_photosArray加入photo是線程安全的蛙埂,至于getter方法使用NSArray保證了array不會(huì)被誤修改.
最后的工程 http://cdn2.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_End_1.zip
如果你覺(jué)得我的這篇文章對(duì)你有一丁點(diǎn)兒作用的話倦畅,那么希望你能在下方給個(gè)贊哈,讓我知道這文章已經(jīng)起了它應(yīng)該的作用绣的,謝謝叠赐!