GCD --換種簡單的方式理解

在開發的過程中,對于多線程的需求越來越高,但是由于在概念的理解上一直都有些許的模糊不清,所以這次也趁著重讀一遍高級編程中的GCD部分,將多線程重新整理了一下。


1. 多線程的理解
2. GCD方法的理解

1. 多線程的理解

多線程的本義很好理解:就是為了多個不同的任務開啟多個線程,讓主線程能及時的響應用戶的操作。但是對于和多線程緊緊相關的異步、同步和并行、串行而言,很容易搞模糊。所以下面還是按照自己的思路一步步進行分析:

1)隊列

官方點的理解:隊列就是任務的集合,采用的是先進先出的原則,每執行完一個任務就會釋放一個任務。通白點理解就是:對于每一個新任務而言,先排到隊伍的末尾,具體該如何執行等按序輪到才知道。

2)同步與異步

① 同步:在當前線程中執行任務,并沒有開啟新線程的能力
② 異步:具有開啟新線程的能力,能在新的線程中執行任務

首先,通過圖例解釋異步執行的過程:


異步執行示范

異步執行并不會堵塞當前的線程,只是告訴當前的線程:

你只要幫我將任務加入queue隊列中即可,任務該如何執行無你無關。

所以從上面圖例中的主線程視角看整個的執行過程,就是:


異步執行時主線程視角

主線程并不會直接看到block中關于任務的內容,只是將dispatch_async和NSLog都當做一行執行命令來執行。也就是說:主線程只管執行各個命令行,而隊列中加入的任務在其他線程中執行:


異步執行打印結果

同步執行正好和異步執行相反,以下面的圖例分析:


同步執行示范
不僅需要將任務加入到queue隊列中,還會強制要求當前線程暫停,來執行該隊列中的任務。

所以結果和執行的順序一致:


同步執行的打印結果
3)隊列類型:串行和并行

① 串行 --->一個任務執行完再執行下一個任務,按序一個個執行。

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

① 并行 --->不等待當前任務執行完就執行下一個任務。

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

同步還是異步、串行還是并行對于任務而言就相當于兩個同等重要的原則。用商店的收銀臺為例來理解:

一家商店的收銀處,同步表示只有一個收銀臺,而異步表示有多個收銀臺。而串行表示管你有幾個
收銀臺,所有人就排成一隊;而并行表示盡量分散,有幾個排幾個。

在對多線程的處理過程中,同步執行其實是很容易出錯的地方。個人理解就是:在當前線程正在處理“將任務加入queue隊列”中時,同步執行會強制性的要求當前線程暫停手中的任務,從而用所有的資源來執行"queue隊列中的任務"。舉例而言:


同步執行易報錯點

主線程正在執行任務時,強制要求執行block中的新任務。


同步執行易報錯點

異步執行創建的線程正在執行任務,強制要求執行同步中的任務。

所以在對同步執行的使用中,要特別注意不要進入到崩潰的邏輯中。

2. GCD方法的理解
dispatch_after
dispatch_after示例

對于該方法而言,有兩個地方需要特別注意:
① NSEC_PER_SEC
dispatch_afer方法中的第二個參數的時間單位并不是秒而是"納秒",所以這也是為什么需要使用系統定義的變量進行轉換的原因。系統提供的裝換方式:

每秒有多少納秒
#define NSEC_PER_SEC 1000000000ull 
每微妙有過少毫秒
#define NSEC_PER_MSEC 1000000ull
每秒有多少毫秒
#define USEC_PER_SEC 1000000ull
每毫秒有多少納秒
#define NSEC_PER_USEC 1000ull

②4秒之后執行?
dispatch_after并不是在指定時間后執行處理,而只是在指定時間內將任務追加到Queue隊列中。
對于上面的圖例而言,該隊列是在主線程的RunLoop中執行,而主線程的RunLoop每隔1/60秒執行,而該Block中的任務要求延遲4秒之后執行,所以最快在4秒 +1/60后執行。并且由于主隊列中有大量的處理或主線程本身就有一定的延遲,所以執行的時間會更長。

所以,dispatch_afer只適用于一些想大致延遲執行的處理中,并不適用于對時間有嚴格要求的情況下。
dispatch_barrier_async

對于多線程而言,最需要關注的就是數據競爭的問題。雖然串行執行能避免該問題,但是現實生活中數據的讀取往往是要求能并行執行的,只有當遇到對數據的寫入時才需要額外的關注。
舉例而言:


并行執行讀取和寫入操作

很顯然,讀取和寫入并行的情況下,讀取的數據會出現錯誤:


簡單的并行執行數據讀取出錯

而dispatch_barrier_async的定義就是:
等待之前的并行執行全部結束之后,再將指定的處理追加到隊列中。等待該函數追加的處理執行完畢之后,隊列才會恢復到一般的動作:


執行結果
所以,dispatch_barrier_async就是等待之前的并行操作執行之后,再單獨的執行該函數中的任務處理,完畢之后就是普通的執行。
Dispatch Semaphore

在實際的開發過程中,經常會出現一種需求:在同一個界面中需要多次請求,在所有請求結束之后再刷新界面。在此之前,我一直都很low的用在一個請求結束時嵌入另一個請求的方法,直到認識到該方法:


信號量的使用

上面的圖例有幾個需要知道的地方:
①dispatch_semaphore_t
Dispatch Semaphore是持有計數的信號,計數為0時等待,計數為1或大于1時,減去1而繼續執行。

首先,創建信號時賦值計數的初始值為0

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

其次,當信號量遇到wait方法時,判斷計數是否大于等于1。如果等于0,進入等待狀態,所以等價于該線程暫停在該處。所以:

[task resume]必須寫在wait方法之前

最后,當任務執行結束,signal函數會將計數值加1,此時wait函數會識別到計數不為0,會減1繼續向下執行。

dispatch_semaphore_signal(semaphore);

信號量配合group使用,當所有的請求結束時,通知主線程進行界面的刷新:


通知主線程進行界面刷新

對于類似多個網絡請求的情況,GCD還提供了另外的一種方法:


多個網絡請求第二種方法
dispatch_group_enter(group);
dispatch_group_leave(group);

關于越底層的東西,越值得深入的思考。GCD的理解也算是基本的入門,也想要更多的去了解這些東西。冬天到了,得好好學習了。

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

推薦閱讀更多精彩內容