標籤:gcd實現 dispatch source
這兩天在看《OC進階編程-多線程編程和記憶體管理》日本人寫的那本,該書對arc,block和gcd有了更深層次的解讀,非常不錯。現在總結一下gcd相關的知識。有關arc和block的參考arc 參考block
網上很多部落格都對gcd有過講解,很多是對gcd的全域隊列,主線程隊列,建立隊列等等,做了單方面的描述,不是很全面系統。下面我們將學習一下系統得gcd。本文主要分為下面幾個要點,前幾個好點比較好理解,最後可能理解起來有些費勁!
● 什麼是gcd,iOS為什麼要用多線程
● 建立線程,序列線程和並發線程
● 系統預設的五個隊列
● gcd的其他介面
● gcd的實現和dispatch source
下面開始介紹第一個要點
1. 什麼是GCD
gcd是非同步執行任務的技術之一。一般將應用程式中記述的線程管理用的代碼在系統級中實現。開發人員只需要定義想執行的任務,並追加到適當的Dispatch Queue中,gcd就能產生必要的線程並計劃執行任務。由於線程管理是系統級實現的。因此可以統一管理,可以執行任務,這樣就比以前的線程更加有效——摘自Apple官方文檔。
在gcd出現之前,就有performSelector還有NSThread。但是performSelector比NSTread要簡單,gcd比performSelector更加簡單,一目瞭然。
本書中給線程下了一個定義:1個CPU執行的CPU指令列為一條無分叉路徑即為“線程”,如:
多線程就是一個程式中有好幾個這樣的無分叉路徑,如
但是多線程是極易發生各種問題的技術,例如資料競爭,死結,線程耗費大量記憶體等等。雖然極易出現問題,但是也應當使用多線程。因為多線程可以保證應用程式的響應效能。
在iOS中App啟動時,最先執行的線程就是主線程,它用來繪製UI,觸控螢幕幕的事件。如果在主線程中進行長時間的處理,就妨礙主線程的執行,從而導致UI卡頓。如
2. 建立線程隊列
一般情況下,不需要手動建立線程隊列,因為系統為了我們準備了2個隊裡(見下個要點)。
這要說明一下Dispatch Queue,它是執行處理的等待隊列。Dispatch Queue有兩種類型,一個是Serial Dispatch Queue順序隊列,一個是Concurrent Dispatch Queue。這兩個都很好理解,前者是串列隊列,一個任務執行完畢,接著下個任務執行。Concurrent Dispatch Queue是並發隊列,
請看下面的代碼:
//dispatch_queue_t gcd = dispatch_queue_create("這是序列隊列", NULL); dispatch_queue_t gcd = dispatch_queue_create("這是並發隊列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(gcd, ^{NSLog(@"b0");}); dispatch_async(gcd, ^{NSLog(@"b1");}); dispatch_async(gcd, ^{NSLog(@"b2");}); dispatch_async(gcd, ^{NSLog(@"b3");}); dispatch_async(gcd, ^{NSLog(@"b4");}); dispatch_async(gcd, ^{NSLog(@"b5");}); dispatch_async(gcd, ^{NSLog(@"b6");}); dispatch_async(gcd, ^{NSLog(@"b7");}); dispatch_async(gcd, ^{NSLog(@"b8");}); dispatch_async(gcd, ^{NSLog(@"b9");}); dispatch_async(gcd, ^{NSLog(@"b10");}); dispatch_release(gcd);
使用不同的Queue輸出結果是不同的。如果是順序隊列,輸出結果肯定是順序的,如果使用並發隊列,每次都不一樣,下面是其中一個log:
b1
b0
b4
b3
b2
b5
b6
b7
b8
b9
之所以Concurrent Dispatch Queue可以做到並發執行,是因為其使用了多個線程,就上面的輸出,可能的方案如下:
剛才的代碼中已經使用dispatch_queue_create函數,看一下dispatch_queue_create的原型:
dispatch_queue_tdispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
這是一個C語言層級的函數。如果第二個參數是NULL表示順序隊列,如果是DISPATCH_QUEUE_CONCURRENT則是並發隊列。通常一個多線程更新相同資源導致資料競爭的時候使用順序隊列,當想並行不發生資料競爭等問題的處理時,使用並發隊列。
注意:Dispatch Queue必須有程式員來釋放。因為ARC不會應用到派發隊列上。可以在create後立即調用dispatch_release();因為block持有這個隊列。當block運行完畢,這個隊列就自動釋放了。
3. 系統預設的五個隊列
實際上,系統會為我們建立幾個隊列,他們是Main Dispatch Queue和Global Dispatch Queue。系統提供的Dispatch Queue總結如下表
下面是擷取全域並發隊列和主線程隊列的代碼
//擷取全域隊列 dispatch_queue_t mainQ = dispatch_get_main_queue(); //擷取高,中,低,後台優先順序隊列並發隊列 dispatch_queue_t globalH = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_queue_t globalD = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t globalL = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_queue_t globalB = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
4. gcd的其他介面介面還是有那麼幾個,有些是常用的,有些則不太常用
dispatch_set_target_queue——改變用dispatch_queue_create建立的隊列的優先順序
dispatch_after——延時處理一段代碼
dispatch_group——並發隊列中,所有的任務執行完成後,調用的代碼
dispatch_barrier_async——柵欄作用。可以將並發隊列中任務分成兩部分。
dispatch_sync——同步等待,當前隊列全部執行完畢
dispatch_apply——規定次數將指定block加入到dispatch_queue中,並等待全部處理執行結束。
dispatch_suspend/dispatch_resume——掛起恢複指定線程隊列
dispatch_semaphore——從名字中可以發現“訊號量”,該介面是對dispatch_barrier_async精細化處理
dispatch_once——只執行一次的代碼。通常用於單例
dispatch I/O——如果想提高檔案讀取速度,可以嘗試dispatch I/O
具體的使用參考下面的代碼。
-(void) testGCD{ [self testDispatch_target]; [self testDispatch_after]; [self testDispatch_Group]; [self testDispatch_Barrier]; [self testDispatch_sync];//在啟動並執行時候,將這一行注釋掉,不然就死結了 [self testDispatch_apply]; [self testDispatch_once];}/* 可以改變dispatch_queue的優先順序 */-(void) testDispatch_target{ dispatch_queue_t serial = dispatch_queue_create("xxxx",NULL); dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_set_target_queue(serial, queueG);}/* testDispatch_after 延時添加到隊列 */-(void) testDispatch_after{ dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"3秒後添加到隊列"); });}/* dispatch_barrier_async 柵欄的作用 */-(void) testDispatch_Barrier{ //dispatch_queue_t gcd = dispatch_queue_create("這是序列隊列", NULL); dispatch_queue_t gcd = dispatch_queue_create("這是並發隊列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(gcd, ^{NSLog(@"b0");}); dispatch_async(gcd, ^{NSLog(@"b1");}); dispatch_async(gcd, ^{NSLog(@"b2");}); dispatch_async(gcd, ^{NSLog(@"b3");}); dispatch_async(gcd, ^{NSLog(@"b4");}); dispatch_barrier_async(gcd, ^{NSLog(@"barrier");});//dispatch_barrier_async dispatch_async(gcd, ^{NSLog(@"b5");}); dispatch_async(gcd, ^{NSLog(@"b6");}); dispatch_async(gcd, ^{NSLog(@"b7");}); dispatch_async(gcd, ^{NSLog(@"b8");}); dispatch_async(gcd, ^{NSLog(@"b9");}); dispatch_async(gcd, ^{NSLog(@"b10");}); dispatch_release(gcd);}/* dispatch_sync.的三個操作 */-(void) testDispatch_sync{ //1. 同步等待 dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_sync(queueG, ^{NSLog(@"dispatch_sync同步等待");}); //2. 死結 dispatch_queue_t mainQ = dispatch_get_main_queue(); dispatch_sync(mainQ, ^{NSLog(@"dispatch_sync同步等待,這麼寫是死結");}); //3. 同樣是死結 dispatch_sync(mainQ, ^{ dispatch_sync(mainQ, ^{NSLog(@"dispatch_sync同步等待,同樣是死結");});});}/* dispatch Group的示範 */-(void) testDispatch_Group{ dispatch_queue_t mainQ = dispatch_get_main_queue(); dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queueG, ^{NSLog(@"dispatch group blk1");}); dispatch_group_async(group, queueG, ^{NSLog(@"dispatch group blk2");}); dispatch_group_notify(group, mainQ, ^{NSLog(@"dispatch group");}); dispatch_release(group);}/* 按照指定次數將指定的block追加到指定的dispatch queue中。 */-(void) testDispatch_apply{ dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queueG, ^(size_t i){NSLog(@"%zu",i);}); NSLog(@"done"); //經典的做法是,迴圈一個數組 NSArray* array = [NSArray arrayWithObjects:@1,@2,@3, nil]; dispatch_apply([array count], queueG, ^(size_t i){ NSLog(@"%ld", [array[i] integerValue]); ;});}/* 執行一次 */-(void) testDispatch_once{ static dispatch_once_t p; dispatch_once(&p,^{ NSLog(@"testDispatch_once"); ;});}
dispatch_suspend/dispatch_resume、dispatch_IO、dispatch_semaphore 這幾個不太常用,就不再過多解釋了
5. gcd的實現和dispatch source
本書中對gcd的實現不清楚,比較笼統和模糊。下面是一些介紹,gcd的實現依賴下面幾個知識:
● 用於管理追加的Block的C語言層實現的FIFO隊列
● Atomic函數中實現的用於排他控制的輕量級訊號
● 用於管理線程的C語言實現的一些容器
當然除了上面說的工具外,gcd還需要核心級的一些實現。系統級中的一些軟體組件比如:libdispatch實現Dispatch queue,Libc(pthreads)實現pthread_workqueue,XNU核心實現workqueue。
編程人員使用的gcd全部API都包含在libdispatch庫中的c語言函數。dispatch queue通過結構體和鏈表實現FIFO隊列,該隊列管理這追加的block。
block並不是直接追加到FIFO中,而是先加入dispatch continuation這一dispatch_continuation_t類型結構體中,然後再假如FIFO隊列。dispatch continuation用於記錄block所屬的一些資訊,類似於執行內容。
本書中以下部分描述了global dispatch queue 、Libc pthread_workqueue和XNU workqueue。書中的意思是,依次逐級調用。
下面說一下dispatch source
gcd中除了dispatch queue以外,還有不太令人信服的dispatch source 。它是BSD系慣有功能kqueue的封裝。kqueue是在XNU核心中發生各種事件時,在應用程式編程方執行處理的技術。其cpu負荷小,盡量不佔用資源。kqueue是應用程式處理XNU核心中發生的各種事件方法中最優秀的一種。
dispatch source可以取消,而dispatch queue不可以取消。
iOS 多線程編程gcd全面系統認識