Objective-C多線程開發(fā)之GCD(最全總結(jié))

什么是CGD呢?以下摘自蘋果的官方說明。

??Grand Central Dispatch (GCD) 是異步執(zhí)行任務(wù)的技術(shù)之一。應(yīng)用程序中記述的線程管理用的代碼是在系統(tǒng)級中實現(xiàn)的。開發(fā)者只需要定義想要執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中,GCD就能生成必要的線程并執(zhí)行任務(wù)。
??也就是說,GCD用我們難以置信的非常簡潔的方法,實現(xiàn)了極為復(fù)雜的多線程編程。本文將羅列GCD的API及其用法實例來幫助大家了解GCD。

1. GCD的隊列和任務(wù)

GCD中有兩個核心的概念:任務(wù)隊列

1.1 任務(wù) (Dispatch Block)

任務(wù): 就是你想讓系統(tǒng)執(zhí)行的操作,GCD中通常是放在dispatch_block_t中的代碼。任務(wù)分為同步執(zhí)行(sync)異步執(zhí)行(async)兩種執(zhí)行方式。

同步執(zhí)行(sync) : 任務(wù)被同步添加到指定的隊列中,在該任務(wù)執(zhí)行結(jié)束前會一直等待。不具備開啟線程的能力,只能在當(dāng)前線程中同步執(zhí)行任務(wù)。

異步執(zhí)行(async) : 任務(wù)被異步添加到指定隊列中,不會等待該任務(wù)執(zhí)行。具備開啟線程的能力,可在新線程中執(zhí)行任務(wù)。

注: 異步執(zhí)行雖然具有開啟新線程的能力,但只有該任務(wù)追加到并發(fā)隊列才會開啟新線程。
1.2 隊列 (Dispatch Queue)

隊列 (Dispatch Queue) : 是執(zhí)行任務(wù)的的等待隊列。開發(fā)者通過 dispatch_asyncdispatch_sync函數(shù)等API,在dispatch_block_t中記述想要執(zhí)行的任務(wù)并將其追加到Dispatch Queue中。Dispatch Queue按照追加的順序(先進先出FIFO,F(xiàn)irst-In-First-Out)執(zhí)行任務(wù)。隊列分為 串行隊列(Serial Dispatch Queue)并發(fā)隊列(Concurrent Dispatch Queue)

串行隊列(Serial Dispatch Queue) : 只開啟一條新的線程,追加到該隊列中的任務(wù)會依次按順序執(zhí)行。

并發(fā)隊列(Concurrent Dispatch Queue) : 會開辟多條新的線程,追加到該隊列中的任務(wù)會并行執(zhí)行。

注: 
1. 并發(fā)隊列雖然有開啟多條新線程的能力,但是只有在異步執(zhí)行任務(wù)時才會開啟新線程。
2. 并發(fā)隊列開啟的新線程個數(shù)并不等同于任務(wù)個數(shù),取決于隊列的任務(wù)數(shù)、CPU核數(shù)、以及CPU負荷等當(dāng)前系統(tǒng)狀態(tài)。

2. GCD任務(wù)的創(chuàng)建、隊列的獲取和創(chuàng)建

2.1 創(chuàng)建同步及異步任務(wù)
dispatch_async(queue, ^{
    //創(chuàng)建異步任務(wù)
});
dispatch_sync(queue, ^{
    //創(chuàng)建同步任務(wù)
});

或者這樣創(chuàng)建:

dispatch_block_t block = ^{
     //任務(wù)
};
dispatch_async(queue, block); //異步執(zhí)行
dispatch_sync(queue, block);  //同步執(zhí)行
2.2 獲取系統(tǒng)主隊列

主隊列為特殊的串行隊列,只有一個線程,即為主線程。主線程用于界面UI更新、用戶事件交互等操作。所以比較耗時的操作(如查詢數(shù)據(jù)庫,數(shù)據(jù)請求等)都不應(yīng)放在主線程中執(zhí)行,會造成頁面卡頓,影響用戶體驗。

dispatch_queue_t queue = dispatch_get_main_queue();
2.3 獲取全局并發(fā)隊列

系統(tǒng)為我們提供了四個全局的并發(fā)隊列,我們可以直接獲取,用來執(zhí)行任務(wù)。

CPU執(zhí)行任務(wù)處理時按照隊列的優(yōu)先級來分配資源,決定任務(wù)執(zhí)行的先后順序。但是蘋果通過XUN內(nèi)核用于Global Dispatch Queue 的線程并不能保證實時性 (Why? I don't know.),因此執(zhí)行優(yōu)先級只是大致的判斷。

//第一個參數(shù)為隊列優(yōu)先級,第二個參數(shù)沒用到,傳0就行。

dispatch_queue_t queue =
 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
全局并發(fā)隊列的優(yōu)先級:

DISPATCH_QUEUE_PRIORITY_HIGH //高優(yōu)先級
DISPATCH_QUEUE_PRIORITY_DEFAULT //默認優(yōu)先級
DISPATCH_QUEUE_PRIORITY_LOW //低優(yōu)先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND //后臺級
2.4 創(chuàng)建 串行隊列(Serial Dispatch Queue)
//1.第一個參數(shù)為隊列名稱,推薦使用工程ID這種逆序命名方式,便于管理和調(diào)試。
//2.第二個參數(shù)傳NULL或DISPATCH_QUEUE_SERIAL,表示串行隊列。

    dispatch_queue_t queue =
     dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_SERIAL);
2.5 創(chuàng)建 并發(fā)隊列(Concurrent Dispatch Queue)
//1.第一個參數(shù)為隊列名稱,推薦使用工程ID這種逆序命名方式,便于管理和調(diào)試。
//2.第二個參數(shù)傳DISPATCH_QUEUE_CONCURRENT,表示并發(fā)隊列。

    dispatch_queue_t queue =
     dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);

3. 不同隊列及任務(wù)的不同執(zhí)行方式對比

GCD 串行隊列(Serial Dispatch Queue) 并發(fā)隊列(Concurrent Dispatch Queue) 主隊列
同步執(zhí)行(sync) 不會開啟新線程,任務(wù)同步執(zhí)行 不會開啟新線程,任務(wù)同步執(zhí)行 主線程發(fā)生死鎖
異步執(zhí)行(async) 開啟一條新線程,任務(wù)同步執(zhí)行 會開啟新線程,任務(wù)并發(fā)執(zhí)行 不會開啟新線程,任務(wù)同步執(zhí)行
由上我們可以看出:要想并發(fā)執(zhí)行某些任務(wù),只有使用 〖并發(fā)隊列 + 異步執(zhí)行〗這種組合方式。
這也是我們開發(fā)中最常用的組合方式。

下面我們來具體試試這幾種組合的應(yīng)用:

3.1 串行隊列 + 同步執(zhí)行
- (void)serialAndSync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);

    dispatch_queue_t queue = \
    dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_sync(queue, block1);
    dispatch_sync(queue, block2);
    dispatch_sync(queue, block3);
    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
1     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
2     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
3     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
end   ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
  • 結(jié)論:
  1. 所有任務(wù)都在主線程中執(zhí)行(【串行+同步】不會開啟新線程)。
  2. 所有任務(wù)按照追加順序依次執(zhí)行。
  3. 任務(wù)執(zhí)行在begin和end之間,同步執(zhí)行任務(wù)會阻塞主線程。
3.2 串行隊列 + 異步執(zhí)行
- (void)serialAndAsync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);

    dispatch_queue_t queue = \
    dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_SERIAL);
    
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_async(queue, block1);
    dispatch_async(queue, block2);
    dispatch_async(queue, block3);

    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d0078800>{number = 1, name = main}
end   ==== >>: <NSThread: 0x1d0078800>{number = 1, name = main}
1     ==== >>: <NSThread: 0x1c0073200>{number = 3, name = (null)}
2     ==== >>: <NSThread: 0x1c0073200>{number = 3, name = (null)}
3     ==== >>: <NSThread: 0x1c0073200>{number = 3, name = (null)}

  • 結(jié)論:
  1. 【串行+異步】只開啟一條新線程,所有任務(wù)按照追加順序依次執(zhí)行。
  2. 任務(wù)執(zhí)行在end之后,異步執(zhí)行任務(wù)不會阻塞主線程。
3.3 并發(fā)隊列 + 同步執(zhí)行
- (void)concurrentAndSync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);
    
    dispatch_queue_t queue = \
    dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_sync(queue, block1);
    dispatch_sync(queue, block2);
    dispatch_sync(queue, block3);
    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
1     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
2     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
3     ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
end   ==== >>: <NSThread: 0x1d4074f80>{number = 1, name = main}
  • 結(jié)論:
  1. 所有任務(wù)都在主線程中執(zhí)行(同步執(zhí)行不會開啟新線程)。
  2. 所有任務(wù)按照追加順序依次執(zhí)行。
  3. 任務(wù)執(zhí)行在begin和end之間,同步執(zhí)行任務(wù)會阻塞主線程。
3.4 并發(fā)隊列 + 異步執(zhí)行
- (void)concurrentAndAsync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);
    
    dispatch_queue_t queue = \
    dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_async(queue, block1);
    dispatch_async(queue, block2);
    dispatch_async(queue, block3);
    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d0263e00>{number = 1, name = main}
end   ==== >>: <NSThread: 0x1d0263e00>{number = 1, name = main}
3     ==== >>: <NSThread: 0x1d4269fc0>{number = 3, name = (null)}
2     ==== >>: <NSThread: 0x1c007f140>{number = 4, name = (null)}
1     ==== >>: <NSThread: 0x1c807e740>{number = 5, name = (null)}
  • 結(jié)論:
  1. 任務(wù)執(zhí)行在end之后 (不在主線程中執(zhí)行,開啟多條新線程執(zhí)行)。不會阻塞主線程
  2. 所有任務(wù)并發(fā)執(zhí)行,不會等待。
3.5 主隊列 + 同步執(zhí)行
- (void)mainAndSync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);
    
    dispatch_queue_t queue = \
    dispatch_get_main_queue();
    
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_sync(queue, block1);
    dispatch_sync(queue, block2);
    dispatch_sync(queue, block3);
    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d0263e00>{number = 1, name = main}
  • 結(jié)論:

發(fā)生死鎖,任務(wù)不會執(zhí)行。由于主隊列為串行隊列,主線程在執(zhí)行mainAndSync 函數(shù),而 mainAndSync 在等待主線程執(zhí)行結(jié)束,就造成了互相等待,均不會執(zhí)行。(開發(fā)中要極力避免這種情況)

3.6 主隊列 + 異步執(zhí)行
- (void)mainAndAsync {
    NSLog(@"begin ==== >>: %@",[NSThread currentThread]);
    
    dispatch_queue_t queue = \
    dispatch_get_main_queue();
    
    dispatch_block_t block1 = ^{
        sleep(2);
        NSLog(@"1     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block2 = ^{
        sleep(1);
        NSLog(@"2     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_block_t block3 = ^{
        NSLog(@"3     ==== >>: %@",[NSThread currentThread]);
    };
    dispatch_async(queue, block1);
    dispatch_async(queue, block2);
    dispatch_async(queue, block3);
    NSLog(@"end   ==== >>: %@",[NSThread currentThread]);
}

輸出結(jié)果:

begin ==== >>: <NSThread: 0x1d4076040>{number = 1, name = main}
end   ==== >>: <NSThread: 0x1d4076040>{number = 1, name = main}
1     ==== >>: <NSThread: 0x1d4076040>{number = 1, name = main}
2     ==== >>: <NSThread: 0x1d4076040>{number = 1, name = main}
3     ==== >>: <NSThread: 0x1d4076040>{number = 1, name = main}
  • 結(jié)論:
  1. 由于主隊列為串行隊列(不會開啟新線程),所有任務(wù)都在主線程中執(zhí)行。
  2. 任務(wù)在end之后執(zhí)行,主隊列中開啟異步任務(wù),不會開啟新線程,會降低異步任務(wù)的優(yōu)先級,在CPU空閑時才會執(zhí)行該任務(wù)。

4. GCD中的 dispatch_set_target_queue 的用法及作用

4.1 更改Dispatch Queue 的執(zhí)行優(yōu)先級

通過 dispatch_queue_create 函數(shù)生成的GCD隊列,不管是 Serial Dispatch Queue 還是 Concurrent Dispatch Queue,其優(yōu)先級都為系統(tǒng)默認優(yōu)先級,若想改變創(chuàng)建隊列的優(yōu)先級,則可以使用 dispatch_set_target_queue 函數(shù)。

    // 第一個參數(shù)為需要更改優(yōu)先級的queue, 第二個參數(shù)為參照隊列,
    // 將參照隊列的優(yōu)先級設(shè)為目標隊列的優(yōu)先級

dispatch_queue_t queue = \
    dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_SERIAL);
    
    dispatch_queue_t globalQueue = \
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    dispatch_set_target_queue(queue, globalQueue);
4.2 設(shè)置多個 Dispatch Queue 的層級

由上我們知道,通過 dispatch_queue_create 函數(shù)創(chuàng)建的 Serial Dispatch Queue 任務(wù)執(zhí)行是同步的,同時只能執(zhí)行一個任務(wù),雖然GCD隊列受到系統(tǒng)資源的限制,但通過 dispatch_queue_create 函數(shù)可以生成任意多個 Dispatch Queue。

當(dāng)生成多個 Serial Dispatch Queue時,各個 Serial Dispatch Queue將并行執(zhí)行。

如下代碼:

- (void)setTargetQueue {
    
    dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("com.example.gcd.queue3", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 ==== >>: %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"2 ==== >>: %@",[NSThread currentThread]);
    });

    dispatch_async(queue2, ^{
        NSLog(@"3 ==== >>: %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"4 ==== >>: %@",[NSThread currentThread]);
    });

    dispatch_async(queue3, ^{
        NSLog(@"5 ==== >>: %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"6 ==== >>: %@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

3 ==== >>: <NSThread: 0x1d0066180>{number = 5, name = (null)}
5 ==== >>: <NSThread: 0x1d4468e80>{number = 4, name = (null)}
1 ==== >>: <NSThread: 0x1d0066700>{number = 3, name = (null)}
6 ==== >>: <NSThread: 0x1d4468e80>{number = 4, name = (null)}
4 ==== >>: <NSThread: 0x1d0066180>{number = 5, name = (null)}
2 ==== >>: <NSThread: 0x1d0066700>{number = 3, name = (null)}

多次運行發(fā)現(xiàn): 其中1、3、5的輸出順序不固定,多個任務(wù)并發(fā)執(zhí)行。

假如此時我想讓多個同步隊列中的任務(wù)還是依次同步執(zhí)行,或者讓多個并發(fā)隊列中的任務(wù)同步執(zhí)行,我該怎么辦呢?對,使用 dispatch_set_target_queue ,將三個隊列指定到同一串行目標隊列上,此時多個隊列任務(wù)就會同步執(zhí)行,不再是并發(fā)執(zhí)行了。

如下代碼:

- (void)setTargetQueue {
    
    dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue3 = dispatch_queue_create("com.example.gcd.queue3", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t targetQueue = dispatch_queue_create("com.example.gcd.targetQueue", DISPATCH_QUEUE_SERIAL);

    //指定到同一串行隊列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 ==== >>: %@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"2 ==== >>: %@",[NSThread currentThread]);
    });

    dispatch_async(queue2, ^{
        NSLog(@"3 ==== >>: %@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"4 ==== >>: %@",[NSThread currentThread]);
    });

    dispatch_async(queue3, ^{
        NSLog(@"5 ==== >>: %@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"6 ==== >>: %@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

1 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}
2 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}
3 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}
4 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}
5 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}
6 ==== >>: <NSThread: 0x1d02602c0>{number = 3, name = (null)}

在必須將不可并行執(zhí)行的多個任務(wù)追加到多個 Serial Dispatch Queue 中時,可使用 dispatch_set_target_queue函數(shù),防止任務(wù)并發(fā)執(zhí)行。

注: 一旦生成 Serial Dispatch Queue 并追加任務(wù)處理,系統(tǒng)對于一個 Serial Dispatch Queue 
就會生成一條線程。假如生成 1000個 Serial Dispatch Queue ,那么就生成了 1000 條線程。
此時會大量消耗內(nèi)存,大幅度降低系統(tǒng)的響應(yīng)性能。

5. GCD時間: dispatch_time_t

dispatch_time_t 為GCD的時間類,用于獲取距離某個目標時間間隔的時間

計算相對時間:

   dispatch_time_t time = \
    dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)

//第一個參數(shù)為某個目標時間,常用 DISPATCH_TIME_NOW,表示現(xiàn)在時間。
//第二個參數(shù)表示具體的時間長度,不能直接傳 int或者float,需 (int64_t)3* NSEC_PER_SEC 這種形式。

注: delta單位是納秒!

NSEC_PER_SEC  1000000000ull  每秒有1000000000納秒
NSEC_PER_MSEC 1000000ull     每毫秒有1000000納秒
USEC_PER_SEC  1000000ull     每秒有1000000微秒
NSEC_PER_USEC 1000ull        每微秒有1000納秒

"ull"是C語言的數(shù)值字面量,是顯示表明類型時使用的字符串(表示“unsigned long long”)。

例如 dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC) 表示距離當(dāng)前時間 3秒后的時間。
計算絕對時間:

dispatch_time_t time = \
    dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>)
    
/* 第一個參數(shù)是一個 timespec 的結(jié)構(gòu)體, 計算的是一個絕對的時間點,比如 2016年10月10日8點30分30秒, 
如果你不需要自某一個特定的時刻開始,可以傳 NUll,表示自動獲取當(dāng)前時區(qū)的當(dāng)前時間作為開始時刻,
第二個參數(shù)同上。*/

例如 dispatch_walltime(NULL, 3*NSEC_PER_SEC)表示距離當(dāng)前時間 3秒后的時間。

struct timespec 類型的時間可以通過NSDate對象生成:

// 通過指定日期獲取一個 dispatch_time_t 對象
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone = 0;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = dispatch_walltime(&time, 0);
    return milestone;
}

6. GCD延時操作: dispatch_after

我們可能經(jīng)常會有這樣的需求,想在3秒后執(zhí)行某項操作,可能不限于3秒,總之,這種想在指定時間后執(zhí)行操作的情況,可用 dispatch_after 來實現(xiàn)。

例如:

//第一個參數(shù)是 dispatch_time_t 的值(詳見5)。
//第二個參數(shù)是執(zhí)行的 Dispatch Queue 。
//第三個參數(shù)是要追加的操作。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"Three seconds later...");
    });

注: dispatch_after 函數(shù)并不是在指定的時間過后執(zhí)行操作,而只是在指定時間后追加操作到 Dispatch Queue 中。

因為 Mian Dispatch Queue 在主線程的RunLoop 中執(zhí)行,假如主線程的刷新頻率為 60 幀,則追加的操作最快在3秒后執(zhí)行,最慢則在 3 + 1/60 秒后執(zhí)行,并且假如在Mian Dispatch Queue 中有大量操作要執(zhí)行或者主線程本身有延遲的話,這個時間會更長。

7. Dispatch Group

7.1 dispatch_group_notify

假如在追加到 Dispatch Queue 中的多個操作全部結(jié)束后想要執(zhí)行某項操作,假如只使用一個 Serial Dispatch Queue時,只需將結(jié)束操作追加到所有任務(wù)的最后即可實現(xiàn)。但是在使用 Concurrent Dispatch Queue或同時使用多個 Dispatch Queue時,實現(xiàn)起來就比較復(fù)雜了。

這時我們就可以使用 Dispatch Group 。下面我們來看一段代碼:

- (void)dispatchGroup {
    NSLog(@"begin  ==== >>: %@",[NSThread currentThread]);

    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(@"Task 1 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"Task 2 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"Task 3 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"Done!  ==== >>: %@",[NSThread currentThread]);
    });
    NSLog(@"end    ==== >>: %@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:

begin  ==== >>: <NSThread: 0x1d006f940>{number = 1, name = main}
end    ==== >>: <NSThread: 0x1d006f940>{number = 1, name = main}
Task 2 ==== >>: <NSThread: 0x1d0473700>{number = 3, name = (null)}
Task 3 ==== >>: <NSThread: 0x1c806cd00>{number = 4, name = (null)}
Task 1 ==== >>: <NSThread: 0x1c406c740>{number = 5, name = (null)}
Done!  ==== >>: <NSThread: 0x1c806cd00>{number = 4, name = (null)}

因為Global Dispatch Queue 為 Concurrent Dispatch Queue ,多個線程并發(fā)執(zhí)行任務(wù),所以任務(wù)執(zhí)行順序不定,但是執(zhí)行結(jié)果 Done 一定是最后執(zhí)行的。無論使用的是什么類型的 Dispatch Queue,Dispatch Group 都可以監(jiān)測到這些任務(wù)執(zhí)行的結(jié)束。

通過 dispatch_group_create() 函數(shù)生成 Dispatch Group .

dispatch_group_asyncdispatch_async 相同,都是追加 Block操作到指定 Dispatch Queue中。不同的是 dispatch_group_async 多了一個 Group 參數(shù)。

7.2 dispatch_group_wait

如上情況我們也可以使用 dispatch_group_wait 函數(shù)去實現(xiàn),如下代碼:

- (void)dispatchGroup {
    NSLog(@"begin  ==== >>: %@",[NSThread currentThread]);

    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, ^{
        sleep(3);
        NSLog(@"Task 1 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"Task 2 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"Task 3 ==== >>: %@",[NSThread currentThread]);
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"Done!  ==== >>: %@",[NSThread currentThread]);
    
    NSLog(@"end    ==== >>: %@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:

begin  ==== >>: <NSThread: 0x1d4262fc0>{number = 1, name = main}
Task 3 ==== >>: <NSThread: 0x1d026f040>{number = 3, name = (null)}
Task 2 ==== >>: <NSThread: 0x1c007ff40>{number = 4, name = (null)}
Task 1 ==== >>: <NSThread: 0x1cc07fe80>{number = 5, name = (null)}
Done!  ==== >>: <NSThread: 0x1d4262fc0>{number = 1, name = main}
end    ==== >>: <NSThread: 0x1d4262fc0>{number = 1, name = main}

從結(jié)果我們可以看出 dispatch_group_wait一樣可以達到我們想要的結(jié)果。但是不同之處也非常明顯,dispatch_group_wait 會阻塞當(dāng)前線程。

另外 dispatch_group_wait 函數(shù)可以指定等待的時間,傳入 DISPATCH_TIME_FOREVER 則表示永遠等待,直到所有任務(wù)執(zhí)行結(jié)束。且該函數(shù)還有 long 型的返回值,返回0則表示所有任務(wù)都執(zhí)行結(jié)束。

如我們修改代碼如下:

long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC));
    
    if (result == 0) {
        //所有任務(wù)都執(zhí)行結(jié)束
        NSLog(@"Done!  ==== >>: %@",[NSThread currentThread]);
    }else {
        //有任務(wù)尚未結(jié)束
        NSLog(@"Not Done!  ==== >>: %@",[NSThread currentThread]);
    }

執(zhí)行結(jié)果為:

begin  ==== >>: <NSThread: 0x1d0264f40>{number = 1, name = main}
Task 3 ==== >>: <NSThread: 0x1d066e980>{number = 3, name = (null)}
Task 2 ==== >>: <NSThread: 0x1cc07f9c0>{number = 4, name = (null)}
Not Done!  ==== >>: <NSThread: 0x1d0264f40>{number = 1, name = main}
end    ==== >>: <NSThread: 0x1d0264f40>{number = 1, name = main}
Task 1 ==== >>: <NSThread: 0x1c807fc80>{number = 5, name = (null)}

由上我們可以看出 dispatch_group_notifydispatch_group_wait 兩個函數(shù)的異同之處。在開發(fā)過程中可根據(jù)具體情況選擇使用。

7.3 dispatch_group_enterdispatch_group_leave

這兩個函數(shù)配對使用,表示 Dispatch Group 開啟任務(wù)和結(jié)束任務(wù)。這兩種通知可以在多線程間自由穿梭,不局限于特定的某個線程。

dispatch_group_enter: 通知group,下面的任務(wù)馬上要放到group中執(zhí)行了。
dispatch_group_leave: 通知group,任務(wù)完成了,該任務(wù)要從group中移除了。

猜測: 可能 Dispatch Group 內(nèi)部有著一個類似引用計數(shù)的存在,調(diào)用 dispatch_group_enter 會加一,調(diào)用 dispatch_group_leave會減一,當(dāng)該值為0時,則調(diào)用 dispatch_group_notifydispatch_group_wait 函數(shù)。

當(dāng)我們異步開啟一個任務(wù),并不指定特定的隊列及線程時,可使用這兩個函數(shù)去通知 Dispatch Group 任務(wù)的開啟和結(jié)束。

如下代碼:

- (void)dispatchGroup {
    NSLog(@"begin  ==== >>: %@",[NSThread currentThread]);

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();

    [self downloadImaegWithUrl:@"http://pic10.photophoto.cn/20090224/0036036802407491_b.jpg" InGroup:group];
    [self downloadImaegWithUrl:@"http://pic25.photophoto.cn/20121220/0036036800277861_b.jpg" InGroup:group];
    [self downloadImaegWithUrl:@"http://img.taopic.com/uploads/allimg/140806/235020-140P60H10661.jpg" InGroup:group];

    dispatch_group_notify(group, queue, ^{
        NSLog(@"Done!  ==== >>: %@",[NSThread currentThread]);
    });
    NSLog(@"end    ==== >>: %@",[NSThread currentThread]);
}

//利用 SDWebImage異步下載圖片
- (void)downloadImaegWithUrl:(NSString *)url InGroup:(dispatch_group_t)group{
    dispatch_group_enter(group);//開啟任務(wù)
    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:url] options:(SDWebImageDownloaderContinueInBackground) progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
        NSLog(@"下載完成! ====>>: %@",[NSThread currentThread]);
        dispatch_group_leave(group);//結(jié)束任務(wù)
    }];
}

執(zhí)行結(jié)果:

begin  ==== >>: <NSThread: 0x1d406ee00>{number = 1, name = main}
end    ==== >>: <NSThread: 0x1d406ee00>{number = 1, name = main}
下載完成! ====>>: <NSThread: 0x1d406ee00>{number = 1, name = main}
下載完成! ====>>: <NSThread: 0x1d406ee00>{number = 1, name = main}
下載完成! ====>>: <NSThread: 0x1d406ee00>{number = 1, name = main}
Done!  ==== >>: <NSThread: 0x1cc069cc0>{number = 3, name = (null)}

8. GCD柵欄 (dispatch_barrier_sync)

當(dāng)我們在訪問數(shù)據(jù)庫或者文件時,使用 Serial Dispatch Queue 可避免數(shù)據(jù)競爭導(dǎo)致程序異常的問題(多個寫入操作不可并行執(zhí)行)。但是這樣的話,程序的執(zhí)行效率就比較低。

為了高效率的進行訪問,我們使用 Concurrent Dispatch Queue,并使用 dispatch_barrier_sync函數(shù)解決數(shù)據(jù)競爭問題。

dispatch_barrier_sync俗稱柵欄,顧名思義,即可以將某些操作隔絕開來,互不影響。

假如我想在4次讀取操作中間加入2次寫入操作, 如下代碼:

- (void)dispatchBarrier {
    
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.example", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"reading1 ===>>: %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"reading2 ===>>: %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        //寫入操作
        NSLog(@"writing1 ===>>: %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        //寫入操作
        NSLog(@"writing2 ===>>: %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"reading3 ===>>: %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"reading4 ===>>: %@",[NSThread currentThread]);
    });
}

Concurrent Dispatch Queue 的性質(zhì)我們可知,4次讀取和2次寫入均會并發(fā)執(zhí)行,先后順序不定,這樣必然會導(dǎo)致數(shù)據(jù)競爭,出現(xiàn)異常。

修改代碼: 我們只需將寫入操作替換成 dispatch_barrier_sync 函數(shù)即可解決該問題。

dispatch_barrier_sync(queue, ^{
        //寫入操作
        NSLog(@"writing1 ===>>: %@",[NSThread currentThread]);
    });
dispatch_barrier_sync(queue, ^{
        //寫入操作
        NSLog(@"writing2 ===>>: %@",[NSThread currentThread]);
    });

dispatch_barrier_sync 函數(shù)會等待追加到 Dispatch Queue 上的并行任務(wù)全部執(zhí)行結(jié)束之后,再將指定的操作追加到 Dispatch Queue 中。該指定的操作執(zhí)行完畢后,Dispatch Queue 才會恢復(fù)正常操作。開始處理其他任務(wù)。

因此修改后的代碼執(zhí)行順序為 reading1和reading2并發(fā)執(zhí)行在前,之后為 writing1 和 writing2 串行執(zhí)行,最后 reading3和reading4并發(fā)執(zhí)行。

9. GCD信號量 (dispatch_semaphore_t)

信號量(dispatch_semaphore_t)可以理解為有信號通過,無信號則等待。具體的我們慢慢來分析。

與信號量有關(guān)的主要有三個函數(shù):

dispatch_semaphore_create(<#long value#>)
//創(chuàng)建一個信號量,傳入一個long型的信號值.
//傳入信號值為大于或者等于0的值,否則將返回 NULL.
dispatch_semaphore_wait(<#dispatch_semaphore_t  _Nonnull dsema#>, <#dispatch_time_t timeout#>)
//與 dispatch_group_wait(詳見7.2)類似,會阻塞當(dāng)前線程,并有返回值.
//當(dāng)當(dāng)前信號量等于0時,持續(xù)等待.
//當(dāng)當(dāng)前信號量大于0時,執(zhí)行返回,并將當(dāng)前信號量減一.
dispatch_semaphore_signal(<#dispatch_semaphore_t  _Nonnull dsema#>)
//該函數(shù)會將當(dāng)前信號量加一.

我理解的信號量主要有三個作用:線程同步線程鎖、控制并發(fā)數(shù).

9.1 線程同步

先看代碼:

__block int count = 0;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        count = 10;
    });
    NSLog(@"%d",count);

由異步并發(fā)執(zhí)行我們可知,此時輸出的count值為 0。假如我想讓主線程保持與任務(wù)異步線程一致,輸出 count 值為10,我該怎么做呢?

修改代碼:

dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
    __block int count = 0;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        count = 10;
        dispatch_semaphore_signal(semphore);
    });
    dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
    NSLog(@"%d",count);

分析:
我們初始化了一個值為0信號量,執(zhí)行 dispatch_semaphore_wait 函數(shù)時發(fā)現(xiàn)信號量為0,則會等待信號,執(zhí)行完 count = 10 后,通過 dispatch_semaphore_signal 函數(shù)獲得一個信號量,此時 dispatch_semaphore_wait 監(jiān)測到信號量大于0,則執(zhí)行返回,線程繼續(xù)執(zhí)行,輸出 count 為10。這就保證了線程間的同步了。

9.2 線程鎖

假如我想異步并發(fā)執(zhí)行1000次任務(wù),給同一塊內(nèi)存區(qū)間賦值,會發(fā)生什么情況呢?
先看代碼:

NSMutableArray *temArr = @[].mutableCopy;
    for (int i = 0; i < 1000; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [temArr addObject:[NSString stringWithFormat:@"%d",i]];
        });
    }

運行會發(fā)現(xiàn)程序異常崩潰的概率相當(dāng)高,這是因為我們異步并發(fā)的操作同一內(nèi)存導(dǎo)致數(shù)據(jù)錯亂引起的異常。

使用線程鎖修改代碼:

dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
    NSMutableArray *temArr = @[].mutableCopy;
    for (int i = 0; i < 1000; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //等待信號
            dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
            //到這里,信號量會減一
            [temArr addObject:[NSString stringWithFormat:@"%d",i]];
            dispatch_semaphore_signal(semphore);
            //信號量加一
        });
    }

通過這種方法,保證了同一時間只有一個操作去改變內(nèi)存。當(dāng)然在實際操作中我們不會這樣去for循環(huán)使用,通常是設(shè)置一個全局的 dispatch_semaphore_t 對象,將需要加鎖的操作放在,dispatch_semaphore_wait 和 dispatch_semaphore_signal 函數(shù)中間。保證唯一性。

例如:

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;

self.semaphore = dispatch_semaphore_create(1);

- (void)setName:(NSString *)name {
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    _name = name;
    dispatch_semaphore_signal(self.semaphore);
}
9.2 控制并發(fā)數(shù)

當(dāng)我們在處理大量線程時,怎么來進行并發(fā)控制呢,假如我有1000個任務(wù)要執(zhí)行,但是同時執(zhí)行的并發(fā)數(shù)我想控制在10個,該怎么做呢?以前我們使用NSOperationQueue可以控制并發(fā)數(shù),在GCD中怎么簡單快速的控制并發(fā)呢?那就是使用 dispatch_semaphore_t.

先看代碼:

- (void)dispatchSemaphore {

    dispatch_group_t group = dispatch_group_create();
    
    dispatch_semaphore_t semphore = dispatch_semaphore_create(10);
    
    for (int i = 0; i < 100; i++) {
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"%d ===>>: %@",i,[NSThread currentThread]);
            sleep(2);
            dispatch_semaphore_signal(semphore);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"end");
}

首先我們創(chuàng)建了一個值為10的信號量,每次循環(huán)會減少一個信號量,執(zhí)行結(jié)束會增加一個信號量,當(dāng)執(zhí)行操作為10個時,此時信號量為0,dispatch_semaphore_wait 函數(shù)將會等待,如此則保證了最多同時有10個任務(wù)在執(zhí)行。由此看出,通過 dispatch_semaphore_t 來控制并發(fā)數(shù)簡單快速又實用。

10. dispatch_suspend / dispatch_resume

當(dāng)追加大量的處理任務(wù)到 Dispatch Queue 時,有時候希望不執(zhí)行某些已經(jīng)追加的任務(wù),在這種情況下,只需要掛起 Dispatch Queue即可,當(dāng)可以執(zhí)行時再恢復(fù)。

  • dispatch_suspend : 掛起指定的 Dispatch Queue??衫斫鉃闀和?zhí)行。
  • dispatch_resume : 恢復(fù)指定的 Dispatch Queue??衫斫鉃槔^續(xù)執(zhí)行。

值得注意的是,dispatch_suspend 對于正在執(zhí)行的任務(wù)是沒有影響的,掛起后,只有 Dispatch Queue 中尚未執(zhí)行的任務(wù)停止執(zhí)行。而恢復(fù)則使得這些任務(wù)能夠繼續(xù)執(zhí)行。

11. GCD快速遍歷 (dispatch_apply)

dispatch_apply 函數(shù)是dispatch_sync函數(shù)和 Dispatch Group的關(guān)聯(lián)API。該函數(shù)按指定次數(shù)將指定的 Block 追加到指定的 Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束。

如下代碼:

- (void)dispatchApply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
    NSLog(@"end");
}

執(zhí)行結(jié)果:

0
2
4
3
5
6
7
8
9
1
end

因為在 Global Dispatch Queue 中執(zhí)行操作,10個任務(wù)并發(fā)執(zhí)行,但是最后的end一定在最后的位置上。因為 dispatch_apply 會等待全部任務(wù)執(zhí)行結(jié)束。

另外,由于dispatch_apply 函數(shù)與 dispatch_sync函數(shù)一樣,會等待任務(wù)結(jié)束,阻塞當(dāng)前線程,因此推薦在 dispatch_async 函數(shù)中異步執(zhí)行 dispatch_apply 函數(shù)。

例如:

- (void)dispatchApply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //異步執(zhí)行
    dispatch_async(queue, ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"%zu",index);
        });
        
        //等待處理結(jié)束
        dispatch_async(dispatch_get_main_queue(), ^{
            //主線程回調(diào),刷新UI
            NSLog(@"end");
        });
    });
    NSLog(@"main");
}

12. GCD單例 (dispatch_once)

dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次指定處理的API。

下面這種經(jīng)常出現(xiàn)的用來進行初始化的代碼可通過 dispatch_once 函數(shù)來簡化。

static int count = 0;
    if (count == 0) {
        //在這里初始化
        count = 100;
    }

使用 dispatch_once 函數(shù):

static int count = 0;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //在這里初始化
        count = 100;
    });

代碼看起來并沒有太大的變化。但是通過 dispatch_once 函數(shù),該代碼即使在復(fù)雜的多線程環(huán)境下執(zhí)行,也可保證百分之百安全。之前的代碼在大多數(shù)情況下也是安全的。但是在多核CPU中,讀取數(shù)據(jù)時,有可能會多次執(zhí)行初始化處理。而用 dispatch_once 函數(shù)就不必擔(dān)心這樣的問題。這就是所說的單例模式,在生成單例對象時使用。


以上是我對于GCD簡單理解之后的總結(jié),若有不正確之處請評論指正,我會盡快驗證修改!

????????????????????????????????------來自一只菜鳥iOS程序猿。

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容