GCD學習筆記

本文來源是基于http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 。這篇文章是基于該博客寫的一個讀書筆記,是對原文章的知識點進行重新梳理加工,加入我的個人理解,以及在工作中使用GCD的一些心得。本人能力有限,若是發(fā)現(xiàn)不足請在評論當中指出,我會在文章中更新,不勝感激!

GCD 特點?

1、GCD 通過將耗時任務放到后臺線程執(zhí)行來提交程序的響應性

2、相對于直接使用線程和鎖的并發(fā)模型,GCD 更易于使用,且能夠避免一些 bug

3、代碼執(zhí)行效率高

4、所有相關的方法都是以dispatch命名開頭

并發(fā)的相關概念

1、串行和并行

串行是同一個時間內(nèi)有且僅有一個任務在執(zhí)行(場景類似單人過一線天,人可以比作任務),并行是同一個時間內(nèi)可以有多個任務在執(zhí)行(場景類似多人百米賽跑,人可以比作任務)。在GCD中,任務可以理解成一個Block。

2、同步和異步

同步是提交任務之后會等待提交的任務執(zhí)行完成之后才做返回操作(場景類似監(jiān)考,監(jiān)考老師從考試開始時就一直呆在考室里面,直到考試結束后才走出考室)。異步就是提交任務之后,不管任務是否開始執(zhí)行,立即做返回操作(場景類似布置作業(yè),老師從上課開始來布置一次作業(yè),然后讓各位同學完成作業(yè),布置完作業(yè)之后,老師馬上就離開了)。

3、臨界值

臨界值可以理解成一塊不能并發(fā)執(zhí)行的代碼,這塊代碼包含了一些共享的資源,不能允許多個線程同時訪問(在幾個口渴的人面前,只有一個水壺,這幾個人只能一個一個直接用水壺喝水,水壺就是臨界值)。

4、競爭條件

簡而言之,多個線程在讀寫共享資源時,執(zhí)行結果取決于線程的執(zhí)行時間和執(zhí)行順序(在幾個口渴的人面前,只有一個水壺,這幾個人只能一個一個直接用水壺喝水,這幾個人當中一個人能喝多少水取決于這個人是什么時候開始喝的水以及喝了多長時間)。

5、死鎖

死鎖是線程中比較悲劇的一個問題,簡單來說就是,線程A等著線程B執(zhí)行完成,線程B反過來等著線程A執(zhí)行完成,好了,這樣線程AB就一直耗著了,這就是死鎖(用一個場景來描述這個死鎖,男生心里暗戀一個女生,碰巧該女生也暗戀這個男生,2個人都在等著對方捅破最后的窗戶紙,最后2人就沒有浪漫的結果了,哈哈)。

6、線程安全

線程安全是一個比較常見的概念,線程安全的代碼能夠在多線程和并發(fā)訪問下不會出現(xiàn)數(shù)據(jù)錯誤等問題,比如NSDictionary就是線程安全,它可以在同一時間內(nèi)被多個線程訪問而不會有任何問題,對比之下NSMutableDictionary就是非線程安全的,在同一個時間內(nèi)只能有且只有一個線程操作它。(GCD的隊列就是線程安全的)

7、上下文切換

上下文切換就是在一個進程內(nèi)執(zhí)行多線程任務時候,線程之間切換時候保存和恢復線程的執(zhí)行狀態(tài),比如從主線程切換到其它線程,這個時候系統(tǒng)會保存主線程的執(zhí)行狀態(tài)(包括不限于變量),從其它線程切換到主線程時候系統(tǒng)會恢復之前保存的主線程執(zhí)行狀態(tài)。

講完了并發(fā)的相關概念,接下來就開始講和GCD直接相關的技術了(在本文中任務與Block是等同的)

隊列

GCD提供了隊列來處理任務(Block),隊列使用FIFO(先進先出)的方法來管理你提交給GCD任務,也就是說在GCD的同一個隊列里面,先提交的任務比慢提交的任務較先執(zhí)行,也就是先來先執(zhí)行。

1、串行隊列(Serial Queues)

顧名思義,串行隊列就是一個時間內(nèi)只執(zhí)行一個任務,只有前一個任務執(zhí)行完成之后,才會開始執(zhí)行下一個任務,任務的被執(zhí)行順序和任務被提交到GCD的順序是一致的。示意圖如下(來自網(wǎng)絡)


行隊列

2、有串行隊列就會有并發(fā)隊列(Concuttent Queues)

并發(fā)隊列允許同一個時間內(nèi)可以有多個任務在執(zhí)行,不過和串行隊列一致的一點就是任務的被執(zhí)行順序和任務被提交到GCD的順序是一致的。對于GCD來說,任務先來先執(zhí)行,不管你是什么類型的隊列。示意圖如下(來自網(wǎng)絡)


并發(fā)隊列

既然知道了并發(fā)隊列和串行隊列,那你也應該會猜到蘋果肯定也會給幾個默認的隊列讓我們這些開發(fā)者使用的,接下來就來說說隊列的類型。系統(tǒng)提供了一個特殊的串行隊列給我們使用,這個隊列就是主線程隊列(main queue),它除了具備串行隊列的一個特點之外(一個時間內(nèi)只能執(zhí)行一個任務,只有前一個任務執(zhí)行完成之后,才會開始執(zhí)行下一個任務)還有一個大殺器,就是所有的任務保證都會在主線程中執(zhí)行,這意味著什么呢?意味著我們可以使用這個隊列來更新UI。(不管iOS還是Android系統(tǒng),所有的UI操作都是要在主線程中來完成的)。

說完串行隊列說說系統(tǒng)提供的并發(fā)隊列吧!名字叫做全局分發(fā)隊列(Global Dispatch Queue),它有四個不同優(yōu)先級別的并發(fā)隊列,優(yōu)先級別分別是Background,low,default,high。使用這個全局分發(fā)隊列的時候并不是設置優(yōu)先級別越高越好,因為蘋果的Api也是使用這些隊列,所以在這些個隊列中已經(jīng)有存在一些任務,通常的使用方法是設置默認的優(yōu)先級別default。

關鍵點 dispatch_async,dispath_sync,串行隊列,并行隊列

講了這么多,還沒有上代碼,好煩。我就圖方便,直接用他人的現(xiàn)有案例來說吧,http://cdn4.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_Start_1.zip ?(初始案例)

這是一個圖片案例,從網(wǎng)絡上加載圖片,加載完成后點擊進入查看大圖,在大圖里面識別出人物的眼睛,并給眼睛貼上圖片。

項目截圖
案例結構說明


在開發(fā)中,為了有較好的用戶體驗,通常會把一些耗時的操作(文件操作,數(shù)據(jù)庫操作,網(wǎng)絡操作,圖片編解碼)放到后臺線程去操作,以避免耗時操作阻塞主線程。在被注釋掉的代碼中直接在UI主線程進行圖片對象的處理(對人物的眼睛進行識別然后貼圖),這個是一個耗時操作,所以對它進行改進。

改進之后,

1、將這個耗時操作放到后臺線程去操作,使用 dispath_async 異步提交任務到全局并發(fā)隊列(優(yōu)先級為高),然后不管任務現(xiàn)在是否開始執(zhí)行,立即執(zhí)行下一個代碼NSLog打印內(nèi)容。

2、在第二個dispath_async中,此時上下文環(huán)境為后臺線程,那么要更新UI的話必須要在主線程內(nèi)更新,所以使用dispath_async異步提交更新UI的任務到主線程(dispatch_get_main_queue()獲取串行主線程隊列),dispath_async為異步執(zhí)行,不會等待任務執(zhí)行完返回,而是立即返回。

3、使用UIImage對象更新UI。

改進之后

從上面的代碼可以看到,dispath_async添加Block到一個隊列中然后馬上返回,這個Block在之后的某個時間點會被GCD執(zhí)行。那么什么時候使用dispath_async呢?網(wǎng)絡操作和CPU密集型的任務都可以放到后臺去執(zhí)行,這樣做的好處就是不會阻塞當前主線程。

dispath_async和各個類型的隊列配合:

1、自定義串行隊列

如果你想要在后臺線程中連續(xù)執(zhí)行任務,并且跟蹤任務的執(zhí)行,那么dispath_async+自定義串行隊列是你的一個不錯的選擇。

自定義串行隊列

2、主線程串行隊列

在后臺并發(fā)隊列執(zhí)行完任務之后,通常會跳到主線程更新UI。

主線程串行隊列

3、后臺線程執(zhí)行耗時的非UI任務

dispath_sync和各個類型的隊列配合:

1、自定義串行隊列,在主線程內(nèi)同步執(zhí)行dispath_sync,將任務提交到自定義串行隊列執(zhí)行。

2、主線程串行隊列,在主線程同步提交任務到主線程串行隊列會造成死鎖,死鎖,死鎖。

代碼中的dispath_sync提交任務到主線程串行隊列之后,沒有馬上返回而是在等待任務執(zhí)行完成。而在主線程串行隊列,已經(jīng)有dispath_sync這個方法在執(zhí)行了,主線程隊列等dispath_sync這個方法執(zhí)行完成后,才執(zhí)行dispath_sync的提交任務。dispath_sync提交的任務等待主線程,主線程等待dispath_sync方法,你等我,我等你,大家一起Over!

3、并發(fā)隊列,dispath_sync和并發(fā)隊列一起使用是無法達到任務在后臺運行的效果的,因為并發(fā)隊列的任務都是在主線程中,并且無法體現(xiàn)出并發(fā)隊列的并發(fā)特性

關鍵點 dispatch_after

有些時候我們會有類似需求,比如在用戶登錄成功幾秒后開始從服務器同步數(shù)據(jù)到本地。可以使用dispatch_after來完成類似延遲任務。在當前案例中,我們使用dispatch_after來延遲做出提示。

先創(chuàng)建一個dispatch_time_t 說明從什么時候開始算起延遲多少時間,延遲時間的單位是毫秒。在延遲時間到達之后dispatch_after提交任務給GCD。GCD根據(jù)情況安排任務的執(zhí)行。也就是說這里的延遲是延遲任務的提交時間,而不是說延遲多少時間后任務開始執(zhí)行。

dispatch_after和各個類型的隊列

1、自定義串行隊列(Custom Serial Queue),不建議在這個隊列中使用。

2、主線程隊列(Main Queue)使用dispatch_after的好地方。

3、并發(fā)隊列(Concurrent Queue,不建議在這個隊列中使用


關鍵點 線程安全的單例對象

在使用單例模式的時候,最重要的就是當有多個線程同時訪問單例對象的時候,單例對象需要時刻保持有且只有一個對象,數(shù)據(jù)讀寫線程安全。先看看案例

這個sharedManager方法如果有多個線程同時訪問的話,那么就會出現(xiàn)創(chuàng)建多個sharedPhotoManager對象的問題。啟用2個并發(fā)任務來訪問sharedManager方法,

sharedManager方法做一些休眠操作和打印內(nèi)存地址操作,這樣的目的就是讓2個線程同時可以訪問sharedManager方法

Xcode輸入的sharedPhotoManager對象內(nèi)存地址表明,創(chuàng)建了3個sharedPhotoManager對象,那么這個作為單例肯定是很坑爹的。

關鍵點 dispatch_once

dispatch_once()方法會以線程安全的方式有且僅有一次執(zhí)行Block里面的方法,這樣當多個線程同時訪問sharedManager方法的時候就不用擔心會出現(xiàn)多個對象被創(chuàng)建的情況了,哈哈,簡單吧!

說完了初始化的問題,接下來就是getter和setter方法的問題了。dispatch barriers是GCD提供的優(yōu)雅的讀寫鎖解決方案。

看圖說話,在dispatch barriers 執(zhí)行前任務是并發(fā)執(zhí)行的,但是到了dispatch barriers后,并發(fā)執(zhí)行全部都變成了串行執(zhí)行,當dispatch barriers執(zhí)行完畢后,任務恢復成以前的狀態(tài)也就是并發(fā)執(zhí)行。也就是說在當前的隊列中,當任務執(zhí)行到dispatch barriers的Block的時候,dispatch barriers可以保證此時有且僅有這個Block在執(zhí)行,當這個Block執(zhí)行成功后,隊列恢復正常。(這個類似多人過獨木橋,有多個人并排走到一座獨木橋前,這個獨木橋每次只能一個人通過,且橋上一次只能站一個人,那么多個人就是單個單個的過獨立橋,過橋之后恢復過橋之前的狀態(tài))。

dispatch barriers和各個類型的隊列

1、自定義串行隊列(Custom Serial Queue), 串行隊列本來就是一次只能執(zhí)行一個任務和dispatch barriers搭配沒有什么用處。

2、并發(fā)隊列(Global Concurrent Queue),系統(tǒng)的API也有使用這些隊列,為了不影響系統(tǒng)API執(zhí)行,最好不要在這些隊列使用。

3、自定義并發(fā)隊列(Custom Concurrent Queue),墻裂推薦,需要保證setter和初始化方法線程安全的都是可以使用這個搭配。


解決getter和setter方法的線程安全問題

dispatch_barrier_async在setter方法保證往_photosArray加入photo是線程安全的,至于getter方法使用NSArray保證了array不會被誤修改.

最后的工程 http://cdn2.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_End_1.zip

如果你覺得我的這篇文章對你有一丁點兒作用的話,那么希望你能在下方給個贊哈,讓我知道這文章已經(jīng)起了它應該的作用,謝謝!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,830評論 1 17
  • 1. iOS中多線程的四種方案 iOS中實現(xiàn)多線程目前有4種方案,最常用的是GCD和NSOperation兩種,而...
    TedX閱讀 1,576評論 3 2
  • GCD 深入理解:第一部分 什么是 GCD GCD 是 libdispatch 的市場名稱,而 libdispat...
    willphonez閱讀 640評論 0 2
  • 雖然 GCD 已經(jīng)出現(xiàn)過一段時間了,但不是每個人都明了其主要內(nèi)容。這是可以理解的;并發(fā)一直很棘手,而 GCD 是基...
    隨風飄蕩的小逗逼閱讀 1,371評論 0 2
  • 1.他怎么結婚了? 曾經(jīng)聽人說,如果有一個人總是出現(xiàn)在你的夢中,那不是說明你想那個人,而是說明那個人想見你。 于是...
    麥筱柒閱讀 3,269評論 13 56