iOS 多線程編程gcd全面系統認識

來源:互聯網
上載者:User

標籤: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全面系統認識

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.