GCD總結(jié)筆記

章節(jié)目錄

  • 什么是GCD?
  • 如何在多條路徑中執(zhí)行CPU命令列?
  • 即使多線程存在很多問題(如數(shù)據(jù)競爭、死鎖、線程過多消耗大量內(nèi)存),為何還要使用多線程?
  • 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實現(xiàn)
  • 參考資料

正文:

  • 什么是GCD?

GCD是異步執(zhí)行任務(wù)的技術(shù)之一。
一般應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)中實現(xiàn)。
所以我們只需定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中,GCD就能生成必要的線程并計劃執(zhí)行任務(wù)。

在導(dǎo)入GCD之前,Cocoa框架提供了NSThread等簡單的多線程編程技術(shù),但通過源碼對比可知GCD更為簡潔,執(zhí)行效率也更高。

一個CPU一次只能執(zhí)行一個命令,不能執(zhí)行某處分開的并列的兩個命令,因此通過CPU執(zhí)行的CPU命令列就好比一條無分叉的大道,其執(zhí)行不會出現(xiàn)分歧。
而在OC的if等控制語句或函數(shù)調(diào)用的情況下,執(zhí)行命令列的地址會遠離當(dāng)前的位置(位置遷移)

一個CPU執(zhí)行的CPU命令列為一條無分叉路徑,即為線程。
多線程即為多條路徑。多線程中,1個CPU核執(zhí)行多條不同路徑上的不同命令。
一個64核的CPU芯片有64個CPU。


CPU命令列.png
  • 一個CPU核一次能夠執(zhí)行的CPU命令始終為1,那如何在多條路徑中執(zhí)行CPU命令列?

OSX和iOS的核心XNU內(nèi)核在發(fā)生操作系統(tǒng)事件時會切換執(zhí)行路徑(如每隔一定時間,喚起系統(tǒng)調(diào)用等情況)。
執(zhí)行中路徑的狀態(tài),如CPU的寄存器等信息保存到各自路徑專用的內(nèi)存塊中,從切換目標(biāo)路徑專用的內(nèi)存塊中,復(fù)原CPU寄存器等信息,繼續(xù)執(zhí)行切換路徑的CPU命令列。這被稱為”上下文切換”。

由于使用多線程的程序可以在某個線程和其他線程之間反復(fù)多次進行上下文切換,因此看上去就好像一個CPU核能夠并列地執(zhí)行多個線程一樣。

但具有多個CPU核時,就不是看上去像了,而是真的多個CPU核并行執(zhí)行多個線程的技術(shù)。

這種利用多線程編程的技術(shù)就被稱為多線程編程。

  • 即使多線程存在很多問題(如數(shù)據(jù)競爭、死鎖、線程過多消耗大量內(nèi)存),為何還要使用多線程?

多線程編程可保證應(yīng)用程序的響應(yīng)性能。
主線程是應(yīng)用程序啟動時最先執(zhí)行的線程,負責(zé)描繪用戶界面、處理觸摸屏幕的事件等。
若在主線程進行長時間的處理,就會妨礙主線程的執(zhí)行,從而導(dǎo)致不能應(yīng)用程序的畫面沒有更新而長時間停滯等問題。

Dispatch Queue : 執(zhí)行處理的等待隊列。根據(jù)FIFO執(zhí)行操作的處理。
在執(zhí)行處理時存在兩種Dispatch Queue,一種是等待現(xiàn)在執(zhí)行中的處理的Serial Dispatch Queue(同步),另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue(異步)。

Dispatch Queue種類.png


并行執(zhí)行,使用多個線程同時執(zhí)行多個處理。
但是并行執(zhí)行的處理數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài)(Concurrent Dispatch Queue)。
iOS和OS X的核心 —— XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù),并只生成所需的線程執(zhí)行處理。
當(dāng)處理結(jié)束,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時,XNU內(nèi)核會結(jié)束不再需要的線程(中間會有個放入線程緩存池的操作)。

  • 如何才能得到Dispatch Queue?

兩種方法。

第一種

通過CGD的API: dispatch_queue_create函數(shù)生成Dispatch Queue
一個Serial Dispatch Queue 同時只能執(zhí)行一個追加處理,但使用 dispatch_queue_create函數(shù)可生成任意多個Dispatch Queue。

當(dāng)生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將并行執(zhí)行。雖然一個隊列只能生成一個處理,但可以有多個隊列同時進行一個處理,即為同時執(zhí)行多個處理。
但不可生成太多,否則會消耗大量內(nèi)存,引起大量的上下文切換,大幅降低系統(tǒng)的響應(yīng)性能。

dispatch_queue_create函數(shù),第一個參數(shù)指定Serial Dispatch Queue的名稱。
第二個參數(shù)若為NULL則生成的是Serial Dispatch Queue,若為DISPATCH_QUEUE_CONCURRENT則生成的是Concurrent Dispatch Queue。

生成的Dispatch Queue必須由程序員負責(zé)釋放,通過dispatch_release(隊列名)釋放???br> 通過dispatch_retain(隊列名)增加引用。
即Dispatch Queue也像OC的引用計數(shù)式內(nèi)存管理一樣,需通過dispatch_retain函數(shù)和dispatch_release函數(shù)的引用計數(shù)來管理內(nèi)存。

GCD的使用.png

如圖所示,在dispatch_async函數(shù)中追加Block到Concurrent Dispatch Queue,并立即通過dispatch_release函數(shù)進行釋放。則該Block通過dispatch_retain函數(shù)持有Dispatch Queue。無論Serial Dispatch Queue還是Concurrent Dispatch Queue都一樣。

于是在dispatch_async函數(shù)中追加Block到Dispatch Queue后,即使立即釋放Dispatch Queue,該Dispatch Queue由于被Block所持有也不會被廢棄,因而Block能夠執(zhí)行。Block執(zhí)行結(jié)束后會釋放Dispatch Queue,此時誰都不持有Dispatch Queue,它也因此會被廢棄。

第二種

獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue(Main Dispatch Queue 和 Global Dispatch Queue)

Main Dispatch Queue是在主線程中執(zhí)行的Dispatch Queue。因主線程只有一個,所以其自然是Serial Dispatch Queue。
追加到Main Dispatch Queue的處理在主線程的RunLoop中執(zhí)行。所以將用戶界面的界面更新等一些必須在主線程中執(zhí)行的處理追加到Main Dispatch Queue使用。

Global Dispatch Queue 是所有應(yīng)用程序都能夠使用的Concurrent Dispatch Queue。
其有四個執(zhí)行優(yōu)先級,分別是高、默認(rèn)、低、后臺。
通過XNU內(nèi)核管理的用于Global Dispatch Queue的線程,將各自使用的Global Dispatch Queue的執(zhí)行優(yōu)先級作為線程的優(yōu)先級使用。
所以在使用時,應(yīng)注意優(yōu)先級的選擇,但通過XNU內(nèi)核用于Global Dispatch Queue的線程并不能保證實時性,因此執(zhí)行優(yōu)先級只是大致的判斷。
列如在處理內(nèi)容的執(zhí)行可有可無時,使用后臺優(yōu)先級的這種。

后臺優(yōu)先級.png
Main Dispatch Queue的獲取:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

Global Dispatch Queue(高優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HEIGH,0);

Global Dispatch Queue(默認(rèn)優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

Global Dispatch Queue(低優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_Low,0);

Global Dispatch Queue(后臺優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

對Main Dispatch Queue 和 Global Dispatch Queue執(zhí)行dispatch_retain 和 dispatch_release函數(shù)不會引起任何變化,也不會有任何問題。
這也是使用其會更輕松的原因。

dispatch_get_global_queue的第二個參數(shù),官方解釋為留待未來使用,非0就可能返回NULL

  • dispatch_set_target_queue

dispatch_queue_create函數(shù)生成的Dispatch Queue 不管是Serial Dispatch Queue 還是 Concurrent Dispatch Queue,其使用的線程優(yōu)先級都與Global Dispatch Queue的優(yōu)先級相同。而變更優(yōu)先級就使用dispatch_set_target_queue函數(shù)。

代碼演示:
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);

第一個參數(shù)的Dispatch Queue的優(yōu)先級會修改為與第二個Dispatch Queue的優(yōu)先級相同。
但第一個參數(shù)若為Main Dispatch Queue 和 Global Dispatch Queue則不知道會出現(xiàn)什么狀況,所以不可指定。

  • dispatch_after

指定多少時間后追加操作到Dispatch Queue,而不是指定時間后執(zhí)行處理。

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函數(shù)獲取從第一個參數(shù)指定的時間開始到第二個參數(shù)指定的毫微妙單位時間后的時間。
第一個參數(shù)是指定時間用的dispatch_time_t類型的值,使用dispatch_time函數(shù)或dispatch_walltime函數(shù)生成
第二個參數(shù)中ull * NSEC_PER_SEC代表秒,使用NSEC_PER_MSEC代表毫秒
如150毫秒:150ull * NSEC_PER_MESC
ull是C語言的數(shù)值字面量,表示unsigned long long

  • dispatch_walltime

dispatch_walltime函數(shù)用于計算絕對時間。比如想指定在X年X月X日X時X分X秒這一絕對時間。

  • Dispatch Group

用于將多個Dispatch Queue 添加到同一個Dispatch Group, 待Dispatch Queue全部執(zhí)行完畢后執(zhí)行某項操作。(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監(jiān)聽group,在group中所有操作執(zhí)行完畢后,將第三個參數(shù)的Block添加到第二個參數(shù)的Dispatch Queue。
dispatch_release(group);釋放組

dispatch_group_create函數(shù)生成dispatch_group_t類型的Dispatch Group。
該Dispatch Group與Dispatch Queue相同,在使用結(jié)束后通過dispatch_release函數(shù)釋放。
dispatch_group_async函數(shù)將Block追加到指定的Dispatch Queue,Block屬于指定的Dispatch Group。
所以Block通過dispatch_retain函數(shù)持有Dispatch Group,于是Block執(zhí)行結(jié)束,該Block就通過dispatch_release函數(shù)釋放持有的Dispatch Group。
一旦Dispatch Group使用結(jié)束,不用考慮Block,立即通過Dispatch_release函數(shù)釋放即可。

  • dispatch_group_wait

在Dispatch Group中使用dispatch_group_wait函數(shù)僅等待全部處理執(zhí)行結(jié)束。
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);
第二個參數(shù)為等待的時間,屬于dispatch_time_t類型的值。DISPATCH_TIME_FOREVER意味永久等待,只要group中的處理尚未結(jié)束就一直等待。
等待的時間也可以為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的全部處理執(zhí)行結(jié)束
}else {
    //屬于Dispatch Group的某一個處理還在執(zhí)行中
}

等待意味著一旦調(diào)用dispatch_group_wait函數(shù),該函數(shù)就處于調(diào)用的狀態(tài)而不返回。
在經(jīng)過指定時間或Dispatch Group的處理全部執(zhí)行結(jié)束之前,執(zhí)行該函數(shù)的線程停止。
若不等待則可以使用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函數(shù)會等到目前為止的并行都處理結(jié)束后,再去執(zhí)行此操作,而在執(zhí)行該寫入操作時禁止再執(zhí)行別的操作。

  • dispatch_sync 與 dispatch_async

dispatch_async 異步,將指定的Block”非同步”的追加到指定的Dispatch Queue,dispatch_async函數(shù)不做任何等待。
dispatch_sync 同步,將指定的Block”同步”追加到指定的Dispatch Queue中,在追加Block結(jié)束之前,dispatch_sync函數(shù)會一直等待。
等待的意思同dispatch_group_wait,該線程直到處理結(jié)束才會返回。
同步主隊列操作會導(dǎo)致死鎖。也就是dispatch_sync(dispatch_get_main_queue(),blk1);
死鎖原因:Main Dispatch Queue會等待主線程中操作執(zhí)行執(zhí)行完畢再執(zhí)行該操作。于是主線程等待該操作結(jié)束才繼續(xù)執(zhí)行,該操作又在等待主線程操作結(jié)束才能執(zhí)行。

同一個隊列,異步操作里嵌套同步操作會發(fā)生Crash。(XCode8 測試)

  • dispatch_apply

該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束。
第二個參數(shù)為重復(fù)次數(shù),第二個參數(shù)為添加到的隊列,第三個參數(shù)為追加的處理

dispatch_apply(10,queue,blk1);
NSLog(@“DrunkenMouse”);

blk1添加10此到隊列queue,待操作全都執(zhí)行完畢后輸出DrunkenMouse
第三個參數(shù)Block為帶有參數(shù)的Block,這是為了區(qū)分添加的Block所用。
由于dispatch_apply函數(shù)與dispatch_sync函數(shù)相同,會等待處理執(zhí)行結(jié)束。
因此推薦在dispatch_async函數(shù)非同步的執(zhí)行dispatch_apply函數(shù)。

  • dispatch_suspend / dispatch_resume

dispatch_suspend(queue)將指定的Dispatch Queue掛起。
即不執(zhí)行追加到該Dispatch Queue中未執(zhí)行的處理,但已經(jīng)執(zhí)行的處理不受影響。
dispatch_resume(queue)恢復(fù)指定的Dispatch Queue

  • Dispatch Semaphore

數(shù)據(jù)信號燈。當(dāng)計數(shù)為0時等待,計數(shù)為1或大于1時,減去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
參數(shù)表示計數(shù)的初始值,由create可看出,同樣需dispatch_release函數(shù)釋放??赏ㄟ^dispatch_retain函數(shù)持有。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
該函數(shù)等待Dispatch Semaphore的計數(shù)值大于或等于1。當(dāng)滿足時計數(shù)減一并從dispatch_semaphore_wait函數(shù)返回。
返回0代表計數(shù)值大于或等于1,計數(shù)減一。否則就是沒返回值。
第二個參數(shù)與dispatch_group_wait函數(shù)等相同,由dispatch_time_t類型值指定等待時間。
計數(shù)值的加1通過dispatch_semaphore_signal函數(shù)

  • dispatch_once

一次執(zhí)行。在程序運行階段,其函數(shù)只會執(zhí)行一次。

  • Dispatch I/0

在讀取較大文件時,可將文件分成合適的大小并使用Global Dispatch Queue并列讀取來提升速度。實現(xiàn)這一功能的就是Dispatch I/O 和 Dispatch Data

  • GCD實現(xiàn)

編程人員所使用GCD的API全部為包含在libdispatch 庫中的C語言函數(shù)。
Dispatch Queue通過結(jié)構(gòu)體和鏈表,被實現(xiàn)為FIFO隊列。
FIFO隊列管理是通過dispatch_async等函數(shù)所追加的Block。
Block并不是直接加入FIFO隊列,而是先加入Dispatch Continuation這一dispatch_continuation_t類型結(jié)構(gòu)體中,然后再加入FIFO隊列。
該DispatchContinuation用于記憶Block所屬的DispatchGroup和其他一些信息,相當(dāng)于一般常說的執(zhí)行上下文。

Global Dispatch Queue有8種:
HighPriority DefaultPriority LowPriority BackgroundPriority
HighOvercommitPriority DefaultOvercommitPriority LowOvercommitPriority BackgroundOvercommitPriority
優(yōu)先級中附有Overcommit的Global Dispatch Queue使用在SerialDispatchQueue中。
如Overcommit這個名稱所示,不管系統(tǒng)狀態(tài)如何,都會強制生成線程的DispatchQueue
這八種Global Dispatch Queue各使用一個pthread_workqueue.
GCD初始化時,使用pthread_workqueue_create_np函數(shù)生成pthread_workqueue.
pthread_workqueue包含在Libc提供的Pthreads API中。其使用bsdthread_register和workq_open系統(tǒng)調(diào)用,在初始化XNU內(nèi)核的work queue之后獲取workqueue信息
XNU內(nèi)核持有4種work queue
WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE

Dispatch Queue中執(zhí)行Block的過程。當(dāng)在Global Dispatch Queue 中執(zhí)行Block時,libdispatch從Global Dispatch Queue自身的FIFO隊列中取出Dispatch Continuation,調(diào)用pthread_workqueue_additem_np函數(shù)。將該Global Dispatch Queue自身、符合其優(yōu)先級的work queue信息以及為執(zhí)行Dispatch Continuation的回調(diào)函數(shù)等傳遞給參數(shù)。

看圖.png


pthread_workqueue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用,通知work queue增加應(yīng)當(dāng)執(zhí)行的項目。根據(jù)該通知,XNU內(nèi)核基于系統(tǒng)狀態(tài)判斷是否要生成線程,如果是Overcommit優(yōu)先級的Global Dispatch Queue,workqueue則始終生成線程。
work queue的線程執(zhí)行pthread_workqueue函數(shù),該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)。在該回調(diào)函數(shù)中執(zhí)行加入到DispatchContinuation的Block
Block執(zhí)行結(jié)束后,進行通知DispatchGroup結(jié)束、釋放DispatchContinuation等處理,開始準(zhǔn)備執(zhí)行加入到GlobalDispatchQueue中的下一個Block。

  • 參考
  • Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理
  • 簡書博客
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容