Grand Central Dispatch(GCD)詳解(轉)

概述


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


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,例如app

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

  

由於主線程等待執行結束,而又要在主線程中執行。因此形成了死鎖下面的代碼不會執行。通常狀況下應使用dispatch_syncdispatch_syncblockdispatch_async

Dispatch Queue

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

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

用代碼詳細說明兩種隊列async

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時輸出oop

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

  • Concurrent Dispatch Queue 
    當上面的queueConcurrent Dispatch Queue時,由於不用等待執行中的處理結束,因此首先執行block0,無論block0是否結束都開始執行後面的block1,不等block1執行結束都執行block2依次類推。覺得是併發執行,其實是開闢了多個線程同時執行多個處理。 
    spa

關於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_creatSerial Dispatch QueueSerial 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);
用來執行等待操做,當開始等待阻止其餘將要進行的操做。直到接受到信號,由於接受信號自加1因此取消等待。 
dispatch_semaphore_waitsemaphore = 0dispatch_semaphore_waitsemaphoredispatch_semaphore_wait

注意 
當執行dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);這行代碼時,若是semaphore大於或者等於1,這行代碼會自動將semaphore減去1。每運行一次semaphore - 1直到semaphore爲0。

  • dispatch_semaphore_signal
dispatch_semaphore_signal(semaphore);
會讓semaphore進行加1的操做。
dispatch_semaphore_signal

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];
    });
相關文章
相關標籤/搜索