Grand Central Dispatch(GCD) 是 Apple 開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統。
1. GCD術語
1.1串行Serial
任務串行執行時,一次只能執行一個任務。
1.2并行Parallelism
并行指多個任務同時執行
多核設備可以同時執行多個線程;單核設備如果想要并行執行任務,就需要運行一個線程,執行環境切換(也稱上下文切換,context switch),運行另外一個線程或進程(process),這通常很快發生,足以形成并行執行的錯覺
1.3并發
并發指task同時(simultaneously)發生,可以被同時執行,但具體是否同時執行由GCD根據當前負載決定
1.4同步Synchronous
同步執行會等待提交的task執行完畢后,再繼續執行后面任務。我們平常調用的方法都是同步方法,如第一行調用doSomething:方法,程序執行第二行時,doSomething:方法肯定執行完畢了。同步執行一些耗時操作時會堵塞當前線程。調用dispatch_sync函數并將任務提交到當前隊列(即dispatch_sync函數所在隊列)會導致死鎖。
因為是同步調用,目標隊列不會復制塊(Block_copy),而只引用塊。為提高性能,同步函數一般盡可能在當前線程調用塊
1.5異步Asynchronous
異步指調用任務后立即返回,不需要等待任務執行。異步不會堵塞當前線程
1.6死鎖Dead Lock
兩個或多個任務、線程都在等待對方完成操作。第一個任務無法完成,因為它在等待第二個任務完成。第二個任務無法完成,因為它在等待第一個任務完成。兩個任務相互等待,這就形成了死鎖。
1.7線程安全Thread Safe
線程安全的代碼可以在多個線程或并發任務中同時執行。非線程安全的代碼一次只能在一個環境中運行。例如,NSArray是線程安全的,可以同時在多個線程或并發任務中執行。而NSMutableArray不是線程安全的,一次只能在一個環境中運行
1.8環境切換Context Switch
Context switch指當在同一個處理器處理不同線程時,存儲和恢復執行狀態的過程。編寫多任務應用程序時,進行上下文切換非常常見,但會產生額外的開銷。
2. 兩種隊列
GCD提供的調度隊列( dispatch queue)是一個類似于對象的結構,用于管理向其提交的任務。
所有的dispatch queue都是先進先出(first in, first out. 簡稱FIFO)的數據結構,因此,隊列中任務運行順序與添加順序一致,即第一個進入隊列的任務,第一個被執行;第二個進入隊列的任務,第二個被執行;以此類推。
所有dispatch queue自身都是線程安全的
GCD的隊列分為串行隊列(Serial Queues)和并發隊列(Concurrent Queues)兩種
兩種隊列都用dispatch_queue_create創建,傳入兩上參數,
第1個參數是隊列名稱(唯一標識符),debugger和性能工具會顯示此處隊列名稱以幫助跟蹤任務執行情況,可為空。
第2個參數用于標識隊列類型,串行/并發。
//串行隊列的創建
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
//并發隊列的創建
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
//主隊列(Main Dispatch Queue)
//主隊列是一種特殊的串行隊列
**所有放在主隊列中的任務,都會放到主線程中執行**
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并行隊列(Global Dispatch Queue)
//第1個參數表示隊列優先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。
//第2個參數暫時沒用,用0即可。
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
隊列是指執行任務的等待隊列,即用來存放任務的隊列。
是一種特殊的線性表,采用 FIFO(先進先出)的原則,由GCD內部自行控制。
同步執行和異步執行 指的都是對一個已經創建的隊列,把任務(代碼/代碼塊)添加進去的方式。
同步執行和異步執行區別:
- 會不會阻塞當前線程,是否等待隊列的任務執行結束
- 是否具備開啟新線程的能力。
**
串行隊列(Serial Dispatch Queue)
每次只執行一個任務,讓任務一個接著一個地執行。一個任務執行完畢后,再執行下一個任務。
只開啟一個新線程(主線程則不開啟新線程,在當前線程執行任務)并發隊列(Concurrent Dispatch Queue)
可以讓多個任務并發(同時)執行。
可以開啟多個線程,并且同時執行任務。同步執行(sync)
同步添加任務到指定的隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的任務完成之后再繼續執行。
只能在當前線程中執行任務,不具備開啟新線程的能力。異步執行(async)
異步添加任務到指定的隊列中,它不會做任何等待,
可以繼續執行任務。
可以在新的線程中執行任務,具備開啟新線程的能力。
2. 任務的創建
GCD 提供了同步執行任務的創建方法dispatch_sync和異步執行任務創建方法dispatch_async。
// 同步執行任務創建方法
dispatch_sync(queue, ^{
// 這里放同步執行任務代碼
});
// 異步執行任務創建方法
dispatch_async(queue, ^{
// 這里放異步執行任務代碼
});
1. 異步執行 + 并行隊列
/**
* 異步執行 + 并行隊列
* 特點:可以開啟多個線程,任務交替(同時)執行。
*/
- (void)asyncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
}
結果 :
所有任務是在打印的
syncConcurrent---begin和syncConcurrent---end之后才執行的。
說明當前線程沒有等待,而是直接開啟了新線程,在新線程中執行任務
總結:
異步執行具備開啟新線程的能力,所以除了當前主線程,系統又開啟了其它線程,任務之間不需要排隊,且是交替/同時執行的。
2. 異步執行 + 串行隊列
/**
* 異步執行 + 串行隊列
* 特點:會開啟新線程,但是因為任務是串行的,執行完一個任務,再執行下一個任務。
*/
- (void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncSerial---end");
}
結果
所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執行的(異步執行不會做任何等待,可以繼續執行任務)。
總結
異步執行具備開啟新線程的能力,但由于是串行隊列所以只開啟一個線程。任務是按順序執行的(串行隊列每次只有一個任務被執行,任務一個接一個按順序執行)
3. 同步執行 + 并行隊列
/**
* 同步執行 + 并行隊列
* 特點:在當前線程中執行任務,不會開啟新線程,執行完一個任務,再執行下一個任務。
**/
- (void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"syncConcurrent---end");
}
結果 :
所有任務都在打印的syncConcurrent---begin
和syncConcurrent---end之間執行的(同步任務需要等待隊列的任務執行結束),所有 任務按順序執行的
總結:
所有任務都是在當前主線程中執行的,沒有開啟新的線程(同步執行不具備開啟新線程的能力)。
按順序執行的原因:雖然并發隊列可以開啟多個線程,并且同時執行多個任務。但是因為隊列本身不能創建新線程,只有當前線程這一個線程(同步任務不具備開啟新線程的能力),所以也就不存在并發。而且當前線程只有等待當前隊列中正在執行的任務執行完畢之后,才能繼續接著執行下面的操作(同步任務需要等待隊列的任務執行結束)。所以任務只能一個接一個按順序執行,不能同時被執行。
可見并發隊列的并發功能只有在異步(dispatch_async)函數下才有效。
4. 同步執行 + 串行隊列
/**
* 同步執行 + 串行隊列
* 特點:不會開啟新線程,在當前線程執行任務。
任務是串行的,執行完一個任務,再執行下一個任務。
*/
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"syncSerial---end");
}
結果
所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間執行(同步任務需要等待隊列的任務執行結束)
總結:
這里的執行原理和步驟圖跟“同步執行+并發隊列”是一樣的,只要是同步執行就沒法開啟新的線程,所以多個任務之間也一樣只能按順序來執行
任務是按順序執行的(串行隊列每次只有一個任務被執行,任務一個接一個按順序執行)。
5. 同步執行+主隊列
/**
* 同步執行 + 主隊列
* 特點(主線程調用):互等卡主不執行。
* 特點(其他線程調用):不會開啟新線程,執行完一個任務,再執行下一個任務。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"syncMain---end");
}
總結:
主線程中同步執行+主隊列
在主線程中執行syncMain方法,相當于把syncMain任務放到了主線程的隊列中。而同步執行會等待當前隊列中的任務執行完畢,才會接著執行。那么當我們把任務1追加到主隊列中,任務1就在等待主線程處理完syncMain任務。而syncMain任務需要等待任務1執行完畢,才能接著執行。
現在的情況就是syncMain任務和任務1都在等對方執行完畢。這樣大家互相等待,所以就卡住了,所以我們的任務執行不了,而且syncMain---end也沒有打印。
6.異步執行 + 主隊列
/**
* 異步執行 + 主隊列
* 特點:只在主線程中執行任務,執行完一個任務,再執行下一個任務
*/
- (void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncMain---end");
}
結果
所有任務是在打印的syncConcurrent—begin和syncConcurrent—end之后才開始執行的(異步執行不會做任何等待,可以繼續執行任務)
總結:
所有任務都是在當前線程(主線程)中執行的,并沒有開啟新的線程(雖然異步執行具備開啟線程的能力,但因為是主隊列,所以所有任務都在主線程中)。
任務是按順序執行的(因為主隊列是串行隊列,每次只有一個任務被執行,任務一個接一個按順序執行)。
任務依賴
一般情況下,異步添加到并行隊列中的任務,我們是沒法控制執行順序的,或者說也是按FIFO順序執行,但執行完成的順序確無法控制。下面是二般情況:
1.Barrier
barrier只能在自己創建的串行/并行隊列中才有效
dispatch_queue_t globalQueue = dispatch_queue_create("ConcurrentQ", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(globalQueue, ^{
NSLog(@"barrier之前的任務1");
});
dispatch_async(globalQueue, ^{
NSLog(@"barrier之前的任務2");
});
/***************************/
//使用dispatch_barrier將任務添加到隊列后,
//任務會在前面任務全部執行完后執行
//且任務在執行過程中,其它任務無法執行
dispatch_barrier_async(globalQueue, ^{
NSLog(@"barrier ConQ 1");
});
/***************************/
dispatch_async(globalQueue, ^{
NSLog(@"barrier之后的任務1");
});
dispatch_async(globalQueue, ^{
NSLog(@"barrier之后的任務2");
});
/***************************/
dispatch_barrier_async(globalQueue, ^{
NSLog(@"barrier ConQ 2");
});
dispatch_barrier_async(globalQueue, ^{
NSLog(@"barrier ConQ 3");
});
//由于是異步添加任務,所以不用等任務執行完,
//有可能會被首先執行打印
NSLog(@"當前隊列的最后一個任務");
2.GCD group
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
---------------dipatch_group------------
//barrier將任務添加到一個隊列中,可以同或異步添加
//group將傷添加到一個組當中,再將組放入隊列,異步添加
dispatch_group_async(group, globalQueue, ^{
NSLog(@"globalQueue1");
});
dispatch_group_async(group, globalQueue, ^{
// sleep是全局休眠定時器
// group_wait只阻塞當前group block,所以3會先執行
dispatch_group_wait(gp, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
NSLog(@"globalQueue2");
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"globalQueue3");
});
/*************當group所有任務完成后,會發送完成通知******************/
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"1.2.3 已完成");
});
//由于是異步添加到并行隊列,這個任務有可能被最先執行完
dispatch_async(globalQueue, ^{
NSLog(@"globalQueue4");
});
或者
項目需求是AB進行網絡請求,數據返回后,回主線程合并,但對網絡請求而言,請求發出即算執行完畢。所以上面的寫法就不更適合。
dispatch_group_t group = dispatch_group_create();
---------------dipatch_group------------
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務完成");
});
網絡請求開始前dispatch_group_enter(group) ,請求成功或失敗后都執行dispatch_group_leave(group)。當所有的enter都leave后會觸發通知。
3.Semaphore
信號量大于0時執行后面代碼,等于0時等待
dispatch_semaphore_create 創建一個semaphore
dispatch_semaphore_signal 發送一個信號,讓信號總量+1
dispatch_semaphore_wait 信號量為0時,等待信號;信號量大于0時,讓信號量-1,并執行后面代碼。
//創建信號量
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
//添加任務1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"第一個任務");
dispatch_semaphore_signal(semaphore1);//信號量+1
});
//添加任務2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//判斷信號量
//如果為0,則根據傳入的時間等待
//如果不為0,則-1執行
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"第二個任務");
dispatch_semaphore_signal(semaphore2);//信號量+1
});
///判斷信號量
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"這是最后一個任務");
});
在網絡請求中所有請求加異步加入到并行隊列后,成功/失敗都要semaphore_signal,然后在異步并行隊列中判斷是否可以執行
dispatch_queue_t queue = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_async(queue,^{
//第一個網絡請求
... ...
dispatch_semaphore_signal(semaphore1);
}
dispatch_async(queue,^{
//第二個網絡請求
... ...
dispatch_semaphore_signal(semaphore2);
}
//semphore_wait并發隊列中執行
dispatch_async(queue,^{
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
//刷新UI
});
});
PS:
dispatch_after是延遲提交,不是延遲運行,所以在串行隊列中,先提交先運行。
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
NSLog(@"Begin add block...");
//提交一個block
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});
//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
//dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
//dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);同樣表示1秒
NSLog(@"After...");
});