GCD簡介

1.隊列

  • 串行隊列,串行隊列將任務以先進先出(FIFO)的順序來執(zhí)行,所以串行隊列經(jīng)常用來做訪問某些特定資源的同步處理。你可以也根據(jù)需要創(chuàng)建多個隊列,而這些隊列相對其他隊列都是并發(fā)執(zhí)行的。換句話說,如果你創(chuàng)建了4個串行隊列,每一個隊列在同一時間都只執(zhí)行一個任務,對這四個任務來說,他們是相互獨立且并發(fā)執(zhí)行的。如果需要創(chuàng)建串行隊列,一般用dispatch_queue_create這個方法來實現(xiàn),并指定隊列類型DISPATCH_QUEUE_SERIAL。

  • 并發(fā)隊列,并發(fā)隊列雖然是能同時執(zhí)行多個任務,但這些任務仍然是按照先到先執(zhí)行(FIFO)的順序來執(zhí)行的。并發(fā)隊列會基于系統(tǒng)負載來合適地選擇并發(fā)執(zhí)行這些任務。并發(fā)隊列一般指的就是全局隊列(Global queue),進程中存在四個全局隊列:高、中(默認)、低、后臺四個優(yōu)先級隊列,可以調用dispatch_get_global_queue函數(shù)傳入優(yōu)先級來訪問隊列。當然我們也可以用dispatch_queue_create,并指定隊列類型DISPATCH_QUEUE_CONCURRENT,來自己創(chuàng)建一個并發(fā)隊列。

  • 主隊列,與主線程功能相同。實際上,提交至main queue的任務會在主線程中執(zhí)行。main queue可以調用dispatch_get_main_queue()來獲得。因為main queue是與主線程相關的,所以這是一個串行隊列。和其它串行隊列一樣,這個隊列中的任務一次只能執(zhí)行一個。它能保證所有的任務都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程。

2.任務

  • 同步任務,使用dispatch_sync將任務加入隊列。將同步任務加入串行隊列,會順序執(zhí)行,一般不這樣做并且在一個任務未結束時調起其它同步任務會死鎖。將同步任務加入并行隊列,會順序執(zhí)行,但是也沒什么意義。

  • 異步任務,使用dispatch_async將任務加入隊列。將異步任務加入串行隊列,會順序執(zhí)行,并且不會出現(xiàn)死鎖問題。將異步任務加入并行隊列,會并行執(zhí)行多個任務,這也是我們最常用的一種方式。

3.GCD常見的用法和應用場景

3.1 dispatch_async(常見的應用場景是異步處理耗時的操作,然后耗時操作處理完畢后,使用主線程更新UI)

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 一個異步的任務,例如網(wǎng)絡請求,耗時的文件操作等等
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI刷新
        ...
    });
});

3.2 dispatch_after (常用的應用場景是延時調用)

dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
    // 在queue里面延遲執(zhí)行的一段代碼
    ...
});

3.3 dispatch_once (常用于單例的創(chuàng)建,只創(chuàng)建一次)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行一次的任務
    ...
});

3.4 dispatch_group (GCD組,把一組任務提交到隊列中,多個請求完畢后才處理事情,如多個網(wǎng)絡請求完畢會,才去更新UI)

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, ^{
    // 異步任務1
});

dispatch_group_async(group, queue, ^{
    // 異步任務2
});

// 等待group中多個異步任務執(zhí)行完畢,做一些事情,介紹兩種方式

// 方式1(不好,會卡住當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...

// 方式2(比較好)
dispatch_group_notify(group, mainQueue, ^{
    // 任務完成后,在主隊列中做一些操作
    ...
});

3.5 dispatch_barrier_async(和dispatch_group類似,dispatch_barrier也是異步任務間的一種同步方式,可以在比如文件的讀寫操作時使用,保證讀操作的準確性。另外,有一點需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊列上有效,在全局(Global)并發(fā)隊列、串行隊列上,效果跟dispatch_(a)sync效果一樣)

// dispatch_barrier_async的作用可以用一個詞概括--承上啟下,它保證此前的任務都先于自己執(zhí)行,此后的任務也遲于自己執(zhí)行。本例中,任務4會在任務1、2、3都執(zhí)行完之后執(zhí)行,而任務5、6會等待任務4執(zhí)行完后執(zhí)行。

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 任務1
    ...
});
dispatch_async(queue, ^{
    // 任務2
    ...
});
dispatch_async(queue, ^{
    // 任務3
    ...
});
dispatch_barrier_async(queue, ^{
    // 任務4
    ...
});
dispatch_async(queue, ^{
    // 任務5
    ...
});
dispatch_async(queue, ^{
    // 任務6
    ...
});

3.6 dispatch_apply(dispatch_apply有什么用呢,因為dispatch_apply并行的運行機制,效率一般快于for循環(huán)的類串行機制(在for一次循環(huán)中的處理任務很多時差距比較大)。比如這可以用來拉取網(wǎng)絡數(shù)據(jù)后提前算出各個控件的大小,防止繪制時計算,提高表單滑動流暢性,如果用for循環(huán),耗時較多,并且每個表單的數(shù)據(jù)沒有依賴關系,所以用dispatch_apply比較好)

// for循環(huán)做一些事情,輸出0123456789
for (int i = 0; i < 10; i ++) {
    NSLog(@"%d", i);
}

// dispatch_apply替換(當且僅當處理順序對處理結果無影響環(huán)境),輸出順序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函數(shù)說明
*
*  @brief  dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關聯(lián)API
*         該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結束
*
*  @param 10    指定重復次數(shù)  指定10次
*  @param queue 追加對象的Dispatch Queue
*  @param index 帶有參數(shù)的Block, index的作用是為了按執(zhí)行的順序區(qū)分各個Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});

3.7 dispatch_suspend和dispatch_resume(隊列的暫停和恢復,已添加到隊列中沒有執(zhí)行的任務不會執(zhí)行,直至等到線程恢復才會繼續(xù)執(zhí)行)

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暫停隊列queue
dispatch_resume(queue);  //恢復隊列queue

3.8 dispatch_semaphore_signal

dispatch_semaphore 信號量基于計數(shù)器的一種多線程同步機制。在多個線程訪問共有資源時候,會因為多線程的特性而引發(fā)數(shù)據(jù)出錯的問題。

// dispatch_semaphore_signal有兩類用法:a、解決同步問題;b、解決有限資源訪問(資源為1,即互斥)問題。
// dispatch_semaphore_wait,若semaphore計數(shù)為0則等待,大于0則使其減1。
// dispatch_semaphore_signal使semaphore計數(shù)加1。

// a、同步問題:輸出肯定為1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    // 任務1
    dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
    NSLog(@"1\n");
    dispatch_semaphore_signal(semaphore2);
    dispatch_semaphore_signal(semaphore1);
});

dispatch_async(queue, ^{
    // 任務2
    dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
    NSLog(@"2\n");
    dispatch_semaphore_signal(semaphore3);
    dispatch_semaphore_signal(semaphore2);
});

dispatch_async(queue, ^{
    // 任務3
    dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
    NSLog(@"3\n");
    dispatch_semaphore_signal(semaphore3);
});

// b、有限資源訪問問題:for循環(huán)看似能創(chuàng)建100個異步任務,實質由于信號限制,最多創(chuàng)建10個異步任務。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
    // 任務
    ...
    dispatch_semaphore_signal(semaphore);
    });
}

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
如果semaphore計數(shù)大于等于1.計數(shù)-1,返回,程序繼續(xù)運行。
如果計數(shù)為0,則等待。
這里設置的等待時間是一直等待。

dispatch_semaphore_signal(semaphore);
計數(shù)+1.
在這兩句代碼中間的執(zhí)行代碼,每次只會允許一個線程進入,這樣就有效的保證了在多線程環(huán)境下,只能有一個線程進入。

3.9 dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f(dispatch_set_context可以為隊列添加上下文數(shù)據(jù),但是因為GCD是C語言接口形式的,所以其context參數(shù)類型是“void *”。需使用上述abc三種方式創(chuàng)建context,并且一般結合dispatch_set_finalizer_f使用,回收context內存)

// dispatch_set_context、dispatch_get_context是為了向隊列中傳遞上下文context服務的。
// dispatch_set_finalizer_f相當于dispatch_object_t的析構函數(shù)。
// 因為context的數(shù)據(jù)不是foundation對象,所以arc不會自動回收,一般在dispatch_set_finalizer_f中手動回收,所以一般講上述三個方法綁定使用。

- (void)test
{
    // 幾種創(chuàng)建context的方式
    // a、用C語言的malloc創(chuàng)建context數(shù)據(jù)。
    // b、用C++的new創(chuàng)建類對象。
    // c、用Objective-C的對象,但是要用__bridge等關鍵字轉為Core Foundation對象。

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    if (queue) {
        // "123"即為傳入的context
        dispatch_set_context(queue, "123");
        dispatch_set_finalizer_f(queue, &xigou);
    }
    dispatch_async(queue, ^{
        char *string = dispatch_get_context(queue);
        NSLog(@"%s", string);
    });
}

// 該函數(shù)會在dispatch_object_t銷毀時調用。
void xigou(void *context)
{
    // 釋放context的內存(對應上述abc)

    // a、CFRelease(context);
    // b、free(context);
    // c、delete context;
}

4. 常見的死鎖

4.1 dispatch_sync

// 假設這段代碼執(zhí)行于主隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 在主隊列添加同步任務
dispatch_sync(mainQueue, ^{
    // 任務
    ...
});

// 在串行隊列添加同步任務 
dispatch_sync(serialQueue, ^{
    // 任務
    ...
    dispatch_sync(serialQueue, ^{
        // 任務
        ...
    });
};

4.2 dispatch_apply

// 因為dispatch_apply會卡住當前線程,內部的dispatch_apply會等待外部,外部的等待內部,所以死鎖。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任務
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任務
        ...
    });
});

4.3 dispatch_barrier

dispatch_barrier_sync在串行隊列和全局并行隊列里面和dispatch_sync同樣的效果,所以需考慮同dispatch_sync一樣的死鎖問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容