Grand Central Dispatch(GCD)詳解

概述


GCD是蘋果異步執行任務技術,將應用程序中的線程管理的代碼在系統級中實現。開發者只需要定義想要執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的線程並計劃執行任務。由於線程管理是作爲系統的一部分來實現的,因此可以統一管理,也可執行任務,這樣比以前的線程更有效率。


GCD的使用


dispatch_sync與dispatch_async

  • dispatch_sync
    synchronous同步,一旦調用dispatch_sync方法,那麼指定的處理(block)追加到指定Dispatch Queue中在執行結束之前該函數都不會返回,也就是說當前的線程會阻塞,等待dispatch_sync在指定線程執行完成後纔會繼續向下執行。

  • dispatch_async
    synchronous異步,一旦調用dispatch_async方法,那麼指定的處理(block)追加到指定的Dispatch Queue中,dispatch_async不會做任何等待立刻返回,當前線程不受影響繼續向下執行。

注意
使用dispatch_sync容易造成死鎖,一般情況下應該使用dispatch_async,例如

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
        NSLog(@"1");
    });
NSLog(@"2");   //這行代碼不會輸出

因爲主線程等待dispatch_sync執行結束,而dispatch_sync又要在主線程中執行block。所以造成了死鎖下面的代碼不會執行。一般情況下應使用dispatch_async


Dispatch Queue

Dispatch Queue是執行處理的等待隊列,通過Block把想要執行的處理追加到Dispatch Queue中,根據追加的順序通過FIFO(先進先出)來執行處理。
有兩種類型的隊列,一種是Serial Dispatch Queue(串行隊列)等待正在執行中的處理當處理結束時再執行隊列中下一個處理。一種是Concurrent Dispatch Queue(併發隊列)不等待現在執行中的處理。

Dispatch Queue種類 說明
Serial Dispatch Queue 等待現在執行中的處理結束
Concurrent Dispatch Queue 不等待現在執行中的處理結束

用代碼詳細說明兩種隊列

dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);

queueSerial Dispatch Queue時輸出

block0
block1
block2
block3
block4

queueConcurrent Dispatch Queue時輸出

block1
block0
block2
block4
block3
  • Serial Dispatch Queue
    當上面的queueSerial Dispatch Queue時按順序執行,先執行block0執行結束後執行block1block2依次類推,因爲是串行執行所以系統此時只開闢了一個線程來處理

  • Concurrent Dispatch Queue
    當上面的queueConcurrent Dispatch Queue時,因爲不用等待執行中的處理結束,所以首先執行block0,不管block0是否結束都開始執行後面的block1,不等block1執行結束都執行block2依次類推。以爲是併發執行,實際上是開闢了多個線程同時執行多個處理。

關於Concurrent Dispatch Queue線程問題
Concurrent Dispatch Queue中並行處理根據Dispatch Queue中的處理數量,機器CPU負載等一些狀態來決定應該開闢多少個線程來處理。假如此時只能開闢三個線程但是要處理5個block事件系統應是下面這樣處理:

線程0 線程1 線程2
block0 block1 block2
block3 block4

此時線程0處理block0,線程1處理block1,線程2處理block2。當block0執行玩後執行block3,可能此時block1還沒有執行完,只有block1執行結束後纔會執行block4。所以說併發隊列執行的順序是不確定的。


Main Dispatch Queue與Global Dispatch Queue

系統已經爲我們提供了幾種Dispatch Queue,不用我們去主動創建。Main Dispatch Queue把處理追加到當前的主線程RunLoop中執行。Global Dispatch Queue是把處理追加到一個Concurrent Dispatch Queue隊列中處理。Global Dispatch Queue有四個優先級,系統提供的Dispatch Queue如下表所示:

名稱 Dispatch Queue 的種類 說明
Main Dispatch Queue Serial Dispatch Queue 主線程執行
Global Dispatch Queue (High Priority) Concurrent Dispatch Queue 執行優先級(高)
Global Dispatch Queue (Default Priority) Concurrent Dispatch Queue 執行優先級(默認)
Global Dispatch Queue (Low Priority) Concurrent Dispatch Queue 執行優先級(低)
Global Dispatch Queue (Background Priority) Concurrent Dispatch Queue 執行優先級(後臺)

系統提供的Dispatch Queue獲取

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalQueueDefau = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_set_target_queue

自己通過dispatch_queue_creat函數創建的Dispatch Queue不管是串行隊列還是併發的隊列的優先級都是與Global Dispatch Queue的默認優先級相同,使用dispatch_set_target_queue可以變更Dispatch Queue的優先級
dispatch_set_target_queue使用

dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serialQueue", NULL);
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue(serialQueue, globalQueueHigh);

上面的代碼實現了通過dispatch_queue_creat創建的串行隊列優先級默認變成了最高優先級,實現的效果是當有多個默認優先級的Serial Dispatch Queue併發執行時,如果設置了某一個Serial Dispatch Queue優先級爲最高,那麼先執行這個最高優先級的隊列,然後再併發執行其他優先級相同的隊列。


dispatch_after

如果想在某一時間後執行某一操作,實現定時器的效果可以用dispatch_after來實現

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
    //從當前時間開始的10秒後
dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"");
    }); //將10秒後將要執行的操作追加到主線程進行執行

注意
上面的代碼中dispatch_after並不是在10秒之後執行某一操作,而是在10秒後把要執行的操作追加到主線程中。比如主線程每0.01秒執一次RunLoop,那麼這個追加操作最快10秒執行,最慢10+0.01秒執行。


Dispatch Group

上面介紹到如果使用Concurrent Dispatch Queue的話是不能確定隊列中任務的執行順序的,如果Concurrent Dispatch Queue中有三個任務要在這三個任務都執行結束後進行某個操作,這時就需要用到Dispatch Group

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
        NSLog(@"block0");
    });
dispatch_group_async(group, queue, ^{
        NSLog(@"block1");
    });
dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"Finish");
    });

代碼執行結果如下

block2
block1
block0
Finish

當追加到Dispatch Group中的處理全部結束時,dispatch_group_notify將會執行追加的Block。


dispatch_group_wait

dispatch_group_notify類似dispatch_group_wait也可以達到相同的效果

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"block0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"Finish");

執行結果如下

block2
block1
block0
Finish

dispatch_group_wait的效果是等待group追加的操作全部執行完後再執行下面的代碼,第二個參數表示等待的時間,DISPATCH_TIME_FOREVER表示一直等待group的處理結果。直到處理完成才執行下面的代碼。
如果只想等待一段指定的時間的話改變DISPATCH_TIME_FOREVER即可

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
         NSLog(@"Finish");
    }
    else{}

上面代碼表示只等待1秒不管group中的處理是否全部完成都要執行下面的代碼,當result = 0表示group中的處理已經處理完成,否則沒有完成。


dispatch_barrier_async

如果在Concurrent Dispatch Queue中追加五個操作,這時想先併發執行前三個操作,等前三個操作都執行結束後再併發的執行後兩個操作,這時就需要用到dispatch_barrier_async函數,具體實現如下:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"block0");
    });
    dispatch_async(queue, ^{
        NSLog(@"block1");
    });
    dispatch_async(queue, ^{
        NSLog(@"block2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier");
    });
    dispatch_async(queue, ^{
        NSLog(@"block3");
    });
    dispatch_async(queue, ^{
        NSLog(@"block4");
    });

代碼執行結果如下

block1
block0
block2
barrier
block3
block4

使用dispatch_barrier_async會等待在它之前追加到Concurrent Dispatch Queue中的所有操作都執行結束之後,再執行在它之後追加到Concurrent Dispatch Queue中的操作。

與Dispatch Group的區別

Dispatch Group是等待追加到它隊列裏面的所有操作執行結束。而dispatch_barrier_async是等待在它之前追加到它對列裏面的操作。一個是等待隊列執行結束,一個是等待隊列中某些操作執行結束。


dispatch_apply

dispatch_apply是按照指定的次數把操作(block)追加到指定的Dispatch Group
示例1

dispatch_queue_t queueSerial = dispatch_queue_create("com.myProject.queueSerial", NULL);
dispatch_apply(5, queueSerial, ^(size_t index) {
        NSLog(@"%zu",index);
    });

運行結果

0
1
2
3
4

示例2

dispatch_queue_t queueCurrnt = dispatch_queue_create("com.myProject.queueCurrnt", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, queueCurrnt, ^(size_t index) {
        NSLog(@"%zu",index);
    });

運行結果

3
0
1
2
4

dispatch_apply第一個參數是要向隊列中追加幾次操作,第二個參數是將要追加操作的次數,第三個參數是用來區分第幾次追加的操作。示例1中是向串行隊列Serial Dispatch Queue追加操作。示例2中是向併發隊列Concurrent Dispatch Queue追加操作。

dispatch_queue_create

在上面的示例中用到了dispatch_queue_create函數這個是用來創建Serial Dispatch QueueConcurrent Dispatch Queue。這個函數第一個參數是隊列的標識符,標識符的寫法最好按照域名倒寫的方法來表示Dispatch Queue,這樣方便在調試中查看。第二個參數表示創建隊列的類型當爲NULL表示創建串行隊列,DISPATCH_QUEUE_CONCURRENT表示創建並行隊列。


dispatch_suspend與dispatch_resume

如果再進行某個操作時,不想執行隊列中的操作,在這個操作完成時再執行隊列中的操作,這時用dispatch_suspend會掛起當前的隊列,此時不會執行隊列中追加的操作。而用dispatch_resume會恢復掛起的隊列。dispatch_suspenddispatch_resume必須成對調用,有掛起就應該有恢復。

    dispatch_queue_t queue = dispatch_queue_create("com.myProject.queueCurrnt", NULL);
    dispatch_suspend(queue);
    dispatch_async(queue, ^{
        for (unsigned int i = 0; i<10; i++) {
            NSLog(@"Concurrent");
        }
    });

    for (unsigned int i = 0; i<10; i++) {
        NSLog(@"Serial");
    }
    dispatch_resume(queue);

如果沒有dispatch_suspenddispatch_resume那麼"Concurrent""Serial"會交替的輸出,如果使用dispatch_suspend會把隊列掛起然後執行下面的代碼當"Serial"全部輸出之後dispatch_resume恢復隊列開始輸出"Concurrent"


Dispatch Semaphore

當用Concurrent Dispatch Queue對數據庫操作時容易發生數據競爭,當有100條數據要進行寫入操作時,因爲是併發操作如果此時正在寫入第1條數據,同時第3條數據也要寫入。這時程序就會發生錯誤,用Dispatch Semaphore可以解決這種問題,當然Dispatch Semaphore不僅僅侷限於此。Dispatch Semaphore類似於信號等待,當對一個對象進行一項操作時,在這操作期間不允許其他操作來訪問對象Dispatch Semaphore會設置一道屏障來阻止其他操作,到操作完成後向Dispatch Semaphore發送一個信號告訴它對象可以進行操作,然後開始進行下一操作,此時Dispatch Semaphore會再次屏蔽其他操作,直到收到對象操作完成的信號。

  • dispatch_semaphore_create
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

創建一個dispatch_semaphore_t類型的對象semaphore,這個就相當於上面所說的信號,它的參數用來判斷是否需要等待。也就是上面所得是否屏蔽其他操作,當參數爲0時,是等待。參數爲1或者大於1時,是不等待。當參數爲0時會一直等待直到收到信號,收到一次信號semaphore會自加1,這樣semaphore大於或者等於1,所以等待取消會執行下面的操作。

  • dispatch_semaphore_wait
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait用來執行等待操作,當semaphore = 0dispatch_semaphore_wait開始等待阻止其他將要進行的操作。直到接受到信號,因爲接受信號semaphore自加1所以dispatch_semaphore_wait取消等待。
注意
當執行dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);這行代碼時,如果semaphore大於或者等於1,這行代碼會自動將semaphore減去1。每運行一次semaphore - 1直到semaphore爲0。

  • dispatch_semaphore_signal
dispatch_semaphore_signal(semaphore);

dispatch_semaphore_signal會讓semaphore進行加1的操作。

Dispatch Semaphore代碼示例

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSMutableArray *array = [NSMutableArray array];
    /**
     創建Dispatch Semaphore 並且設置semaphore爲1
     */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    for (unsigned int i = 0; i<100; i++) {
        dispatch_async(queue, ^{
            /**
             因爲semaphore爲1所以dispatch_semaphore_wait不等待執行下面的操作
             semaphore自減1此時semaphore==0 下一次操作將會等待,一直等到semaphore >= 1
             */
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

            [array addObject:[[NSObject alloc] init]];
            /**
             上面添加數據執行完後dispatch_semaphore_signal操作會使semaphore加1
             此時其他線程中的dispatch_semaphore_wait因爲semaphore = 1 所以取消等待執行下面操作
             */
            dispatch_semaphore_signal(semaphore);
        });

    }

dispatch_once

如果在應用中一段代碼只想讓它執行一次,那麼就需要用到dispatch_once,一般用於創建單例。

    static NSObject *object = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object=[[NSObject alloc] init];
    });

本篇博文參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章