在開發的過程中,對于多線程的需求越來越高,但是由于在概念的理解上一直都有些許的模糊不清,所以這次也趁著重讀一遍高級編程中的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
對于該方法而言,有兩個地方需要特別注意:
① 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的理解也算是基本的入門,也想要更多的去了解這些東西。冬天到了,得好好學習了。