章節目錄
- 什么是GCD?
- 如何在多條路徑中執行CPU命令列?
- 即使多線程存在很多問題(如數據競爭、死鎖、線程過多消耗大量內存),為何還要使用多線程?
- Dispatch Queue
- dispatch_set_target_queue
- dispatch_after
- dispatch_time
- dispatch_walltime
- Dispatch Group
- dispatch_group_wait
- dispatch_barrier_async
- dispatch_sync 與 dispatch_async
- dispatch_apply
- dispatch_suspend / dispatch_resume
- Dispatch Semaphore
- dispatch_once
- Dispatch I/0
- GCD實現
- 參考資料
正文:
-
什么是GCD?
GCD是異步執行任務的技術之一。
一般應用程序中記述的線程管理用的代碼在系統中實現。
所以我們只需定義想執行的任務并追加到適當的Dispatch Queue中,GCD就能生成必要的線程并計劃執行任務。
在導入GCD之前,Cocoa框架提供了NSThread等簡單的多線程編程技術,但通過源碼對比可知GCD更為簡潔,執行效率也更高。
一個CPU一次只能執行一個命令,不能執行某處分開的并列的兩個命令,因此通過CPU執行的CPU命令列就好比一條無分叉的大道,其執行不會出現分歧。
而在OC的if等控制語句或函數調用的情況下,執行命令列的地址會遠離當前的位置(位置遷移)
一個CPU執行的CPU命令列為一條無分叉路徑,即為線程。
多線程即為多條路徑。多線程中,1個CPU核執行多條不同路徑上的不同命令。
一個64核的CPU芯片有64個CPU。
-
一個CPU核一次能夠執行的CPU命令始終為1,那如何在多條路徑中執行CPU命令列?
OSX和iOS的核心XNU內核在發生操作系統事件時會切換執行路徑(如每隔一定時間,喚起系統調用等情況)。
執行中路徑的狀態,如CPU的寄存器等信息保存到各自路徑專用的內存塊中,從切換目標路徑專用的內存塊中,復原CPU寄存器等信息,繼續執行切換路徑的CPU命令列。這被稱為”上下文切換”。
由于使用多線程的程序可以在某個線程和其他線程之間反復多次進行上下文切換,因此看上去就好像一個CPU核能夠并列地執行多個線程一樣。
但具有多個CPU核時,就不是看上去像了,而是真的多個CPU核并行執行多個線程的技術。
這種利用多線程編程的技術就被稱為多線程編程。
-
即使多線程存在很多問題(如數據競爭、死鎖、線程過多消耗大量內存),為何還要使用多線程?
多線程編程可保證應用程序的響應性能。
主線程是應用程序啟動時最先執行的線程,負責描繪用戶界面、處理觸摸屏幕的事件等。
若在主線程進行長時間的處理,就會妨礙主線程的執行,從而導致不能應用程序的畫面沒有更新而長時間停滯等問題。
Dispatch Queue : 執行處理的等待隊列。根據FIFO執行操作的處理。
在執行處理時存在兩種Dispatch Queue,一種是等待現在執行中的處理的Serial Dispatch Queue(同步),另一種是不等待現在執行中處理的Concurrent Dispatch Queue(異步)。
并行執行,使用多個線程同時執行多個處理。
但是并行執行的處理數量取決于當前系統的狀態(Concurrent Dispatch Queue)。
iOS和OS X的核心 —— XNU內核決定應當使用的線程數,并只生成所需的線程執行處理。
當處理結束,應當執行的處理數減少時,XNU內核會結束不再需要的線程(中間會有個放入線程緩存池的操作)。
-
如何才能得到Dispatch Queue?
兩種方法。
第一種
通過CGD的API: dispatch_queue_create函數生成Dispatch Queue
一個Serial Dispatch Queue 同時只能執行一個追加處理,但使用 dispatch_queue_create函數可生成任意多個Dispatch Queue。
當生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將并行執行。雖然一個隊列只能生成一個處理,但可以有多個隊列同時進行一個處理,即為同時執行多個處理。
但不可生成太多,否則會消耗大量內存,引起大量的上下文切換,大幅降低系統的響應性能。
dispatch_queue_create函數,第一個參數指定Serial Dispatch Queue的名稱。
第二個參數若為NULL則生成的是Serial Dispatch Queue,若為DISPATCH_QUEUE_CONCURRENT則生成的是Concurrent Dispatch Queue。
生成的Dispatch Queue必須由程序員負責釋放,通過dispatch_release(隊列名)釋放。可
通過dispatch_retain(隊列名)增加引用。
即Dispatch Queue也像OC的引用計數式內存管理一樣,需通過dispatch_retain函數和dispatch_release函數的引用計數來管理內存。
如圖所示,在dispatch_async函數中追加Block到Concurrent Dispatch Queue,并立即通過dispatch_release函數進行釋放。則該Block通過dispatch_retain函數持有Dispatch Queue。無論Serial Dispatch Queue還是Concurrent Dispatch Queue都一樣。
于是在dispatch_async函數中追加Block到Dispatch Queue后,即使立即釋放Dispatch Queue,該Dispatch Queue由于被Block所持有也不會被廢棄,因而Block能夠執行。Block執行結束后會釋放Dispatch Queue,此時誰都不持有Dispatch Queue,它也因此會被廢棄。
第二種
獲取系統標準提供的Dispatch Queue(Main Dispatch Queue 和 Global Dispatch Queue)
Main Dispatch Queue是在主線程中執行的Dispatch Queue。因主線程只有一個,所以其自然是Serial Dispatch Queue。
追加到Main Dispatch Queue的處理在主線程的RunLoop中執行。所以將用戶界面的界面更新等一些必須在主線程中執行的處理追加到Main Dispatch Queue使用。
Global Dispatch Queue 是所有應用程序都能夠使用的Concurrent Dispatch Queue。
其有四個執行優先級,分別是高、默認、低、后臺。
通過XNU內核管理的用于Global Dispatch Queue的線程,將各自使用的Global Dispatch Queue的執行優先級作為線程的優先級使用。
所以在使用時,應注意優先級的選擇,但通過XNU內核用于Global Dispatch Queue的線程并不能保證實時性,因此執行優先級只是大致的判斷。
列如在處理內容的執行可有可無時,使用后臺優先級的這種。
Main Dispatch Queue的獲取:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
Global Dispatch Queue(高優先級)的獲取方法
dispatch queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HEIGH,0);
Global Dispatch Queue(默認優先級)的獲取方法
dispatch queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
Global Dispatch Queue(低優先級)的獲取方法
dispatch queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_Low,0);
Global Dispatch Queue(后臺優先級)的獲取方法
dispatch queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
對Main Dispatch Queue 和 Global Dispatch Queue執行dispatch_retain 和 dispatch_release函數不會引起任何變化,也不會有任何問題。
這也是使用其會更輕松的原因。
dispatch_get_global_queue的第二個參數,官方解釋為留待未來使用,非0就可能返回NULL
-
dispatch_set_target_queue
dispatch_queue_create函數生成的Dispatch Queue 不管是Serial Dispatch Queue 還是 Concurrent Dispatch Queue,其使用的線程優先級都與Global Dispatch Queue的優先級相同。而變更優先級就使用dispatch_set_target_queue函數。
代碼演示:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“DrunkenMouse”,NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue,globalDispatchQueueBackground);
第一個參數的Dispatch Queue的優先級會修改為與第二個Dispatch Queue的優先級相同。
但第一個參數若為Main Dispatch Queue 和 Global Dispatch Queue則不知道會出現什么狀況,所以不可指定。
-
dispatch_after
指定多少時間后追加操作到Dispatch Queue,而不是指定時間后執行處理。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull * NSEC_PER_SEC);
dispatch_after(time,dispatch_get_main_queue(),^{
NSLog(@“wait at least 3 seconds”);
});
此源碼意為3秒后追加Block操作到Main Dispatch Queue。
-
dispatch_time
dispatch_time函數獲取從第一個參數指定的時間開始到第二個參數指定的毫微妙單位時間后的時間。
第一個參數是指定時間用的dispatch_time_t類型的值,使用dispatch_time函數或dispatch_walltime函數生成
第二個參數中ull * NSEC_PER_SEC代表秒,使用NSEC_PER_MSEC代表毫秒
如150毫秒:150ull * NSEC_PER_MESC
ull是C語言的數值字面量,表示unsigned long long
-
dispatch_walltime
dispatch_walltime函數用于計算絕對時間。比如想指定在X年X月X日X時X分X秒這一絕對時間。
-
Dispatch Group
用于將多個Dispatch Queue 添加到同一個Dispatch Group, 待Dispatch Queue全部執行完畢后執行某項操作。(Serial Dispatch Queue或 Concurrent Dispatch Queue皆可)
dispatch_group_t group = dispatch_group_create(); 生成group
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse”);}); 將操作添加到隊列queue,將隊列queue添加到組group中
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse2”);}); 組中可添加多個queue
dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@“done”);}); 通過notify監聽group,在group中所有操作執行完畢后,將第三個參數的Block添加到第二個參數的Dispatch Queue。
dispatch_release(group);釋放組
dispatch_group_create函數生成dispatch_group_t類型的Dispatch Group。
該Dispatch Group與Dispatch Queue相同,在使用結束后通過dispatch_release函數釋放。
dispatch_group_async函數將Block追加到指定的Dispatch Queue,Block屬于指定的Dispatch Group。
所以Block通過dispatch_retain函數持有Dispatch Group,于是Block執行結束,該Block就通過dispatch_release函數釋放持有的Dispatch Group。
一旦Dispatch Group使用結束,不用考慮Block,立即通過Dispatch_release函數釋放即可。
-
dispatch_group_wait
在Dispatch Group中使用dispatch_group_wait函數僅等待全部處理執行結束。
dispatch_group_t group = dispatch_group_create(); 生成group
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse”);}); 將操作添加到隊列queue,將隊列queue添加到組group中
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse2”);}); 組中可添加多個queue
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
第二個參數為等待的時間,屬于dispatch_time_t類型的值。DISPATCH_TIME_FOREVER意味永久等待,只要group中的處理尚未結束就一直等待。
等待的時間也可以為1秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0) {
//屬于Dispatch Group的全部處理執行結束
}else {
//屬于Dispatch Group的某一個處理還在執行中
}
等待意味著一旦調用dispatch_group_wait函數,該函數就處于調用的狀態而不返回。
在經過指定時間或Dispatch Group的處理全部執行結束之前,執行該函數的線程停止。
若不等待則可以使用DISPATCH_TIME_NOW:
long result = dispatch_group_wait(group,DISPATCH_TIME_NOW);
-
dispatch_barrier_async
dispatch_barrier_async 柵欄,可保證寫入時不會讀取,讀取時不會寫入。
進而可將寫入操作放到Serial Dispatch Queue中,讀取操作放入Serial Dispatch Queue,以提高性能。
dispatch_async(queue,blk1_reading);
dispatch_async(queue,blk2_reading);
dispatch_barrier_async(queue,blk3_writing);
dispatch_async(queue,blk4_reading);
dispatch_barrier_async函數會等到目前為止的并行都處理結束后,再去執行此操作,而在執行該寫入操作時禁止再執行別的操作。
-
dispatch_sync 與 dispatch_async
dispatch_async 異步,將指定的Block”非同步”的追加到指定的Dispatch Queue,dispatch_async函數不做任何等待。
dispatch_sync 同步,將指定的Block”同步”追加到指定的Dispatch Queue中,在追加Block結束之前,dispatch_sync函數會一直等待。
等待的意思同dispatch_group_wait,該線程直到處理結束才會返回。
同步主隊列操作會導致死鎖。也就是dispatch_sync(dispatch_get_main_queue(),blk1);
死鎖原因:Main Dispatch Queue會等待主線程中操作執行執行完畢再執行該操作。于是主線程等待該操作結束才繼續執行,該操作又在等待主線程操作結束才能執行。
同一個隊列,異步操作里嵌套同步操作會發生Crash。(XCode8 測試)
-
dispatch_apply
該函數按指定的次數將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執行結束。
第二個參數為重復次數,第二個參數為添加到的隊列,第三個參數為追加的處理
dispatch_apply(10,queue,blk1);
NSLog(@“DrunkenMouse”);
blk1添加10此到隊列queue,待操作全都執行完畢后輸出DrunkenMouse
第三個參數Block為帶有參數的Block,這是為了區分添加的Block所用。
由于dispatch_apply函數與dispatch_sync函數相同,會等待處理執行結束。
因此推薦在dispatch_async函數非同步的執行dispatch_apply函數。
-
dispatch_suspend / dispatch_resume
dispatch_suspend(queue)將指定的Dispatch Queue掛起。
即不執行追加到該Dispatch Queue中未執行的處理,但已經執行的處理不受影響。
dispatch_resume(queue)恢復指定的Dispatch Queue
-
Dispatch Semaphore
數據信號燈。當計數為0時等待,計數為1或大于1時,減去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
參數表示計數的初始值,由create可看出,同樣需dispatch_release函數釋放。可通過dispatch_retain函數持有。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
該函數等待Dispatch Semaphore的計數值大于或等于1。當滿足時計數減一并從dispatch_semaphore_wait函數返回。
返回0代表計數值大于或等于1,計數減一。否則就是沒返回值。
第二個參數與dispatch_group_wait函數等相同,由dispatch_time_t類型值指定等待時間。
計數值的加1通過dispatch_semaphore_signal函數
-
dispatch_once
一次執行。在程序運行階段,其函數只會執行一次。
-
Dispatch I/0
在讀取較大文件時,可將文件分成合適的大小并使用Global Dispatch Queue并列讀取來提升速度。實現這一功能的就是Dispatch I/O 和 Dispatch Data
-
GCD實現
編程人員所使用GCD的API全部為包含在libdispatch 庫中的C語言函數。
Dispatch Queue通過結構體和鏈表,被實現為FIFO隊列。
FIFO隊列管理是通過dispatch_async等函數所追加的Block。
Block并不是直接加入FIFO隊列,而是先加入Dispatch Continuation這一dispatch_continuation_t類型結構體中,然后再加入FIFO隊列。
該DispatchContinuation用于記憶Block所屬的DispatchGroup和其他一些信息,相當于一般常說的執行上下文。
Global Dispatch Queue有8種:
HighPriority DefaultPriority LowPriority BackgroundPriority
HighOvercommitPriority DefaultOvercommitPriority LowOvercommitPriority BackgroundOvercommitPriority
優先級中附有Overcommit的Global Dispatch Queue使用在SerialDispatchQueue中。
如Overcommit這個名稱所示,不管系統狀態如何,都會強制生成線程的DispatchQueue
這八種Global Dispatch Queue各使用一個pthread_workqueue.
GCD初始化時,使用pthread_workqueue_create_np函數生成pthread_workqueue.
pthread_workqueue包含在Libc提供的Pthreads API中。其使用bsdthread_register和workq_open系統調用,在初始化XNU內核的work queue之后獲取workqueue信息
XNU內核持有4種work queue
WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE
Dispatch Queue中執行Block的過程。當在Global Dispatch Queue 中執行Block時,libdispatch從Global Dispatch Queue自身的FIFO隊列中取出Dispatch Continuation,調用pthread_workqueue_additem_np函數。將該Global Dispatch Queue自身、符合其優先級的work queue信息以及為執行Dispatch Continuation的回調函數等傳遞給參數。
pthread_workqueue_additem_np函數使用workq_kernreturn系統調用,通知work queue增加應當執行的項目。根據該通知,XNU內核基于系統狀態判斷是否要生成線程,如果是Overcommit優先級的Global Dispatch Queue,workqueue則始終生成線程。
work queue的線程執行pthread_workqueue函數,該函數調用libdispatch的回調函數。在該回調函數中執行加入到DispatchContinuation的Block
Block執行結束后,進行通知DispatchGroup結束、釋放DispatchContinuation等處理,開始準備執行加入到GlobalDispatchQueue中的下一個Block。
參考
- Objective-C高級編程:iOS與OS X多線程和內存管理
- 簡書博客