什么是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_async或dispatch_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é)論:
- 所有任務(wù)都在主線程中執(zhí)行(【串行+同步】不會開啟新線程)。
- 所有任務(wù)按照追加順序依次執(zhí)行。
- 任務(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é)論:
- 【串行+異步】只開啟一條新線程,所有任務(wù)按照追加順序依次執(zhí)行。
- 任務(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é)論:
- 所有任務(wù)都在主線程中執(zhí)行(同步執(zhí)行不會開啟新線程)。
- 所有任務(wù)按照追加順序依次執(zhí)行。
- 任務(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é)論:
- 任務(wù)執(zhí)行在end之后 (不在主線程中執(zhí)行,開啟多條新線程執(zhí)行)。不會阻塞主線程
- 所有任務(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é)論:
- 由于主隊列為串行隊列(不會開啟新線程),所有任務(wù)都在主線程中執(zhí)行。
- 任務(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_async與dispatch_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_notify 與 dispatch_group_wait 兩個函數(shù)的異同之處。在開發(fā)過程中可根據(jù)具體情況選擇使用。
7.3 dispatch_group_enter 和 dispatch_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_notify 和 dispatch_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程序猿。