概述
GCD
是蘋果異步執行任務技術,將應用程序中的線程管理的代碼在系統級中實現。開發者只需要定義想要執行的任務並追加到適當的Dispatch Queue
中,GCD
就能生成必要的線程並計劃執行任務。由於線程管理是作爲系統的一部分來實現的,因此可以統一管理,也可執行任務,這樣比以前的線程更有效率。
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
,例如
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1");
});
NSLog(@"2"); //這行代碼不會輸出
因爲主線程等待dispatch_sync
執行結束,而dispatch_sync
又要在主線程中執行block
。所以造成了死鎖下面的代碼不會執行。一般情況下應使用dispatch_async
。
Dispatch Queue
Dispatch Queue
是執行處理的等待隊列,通過Block
把想要執行的處理追加到Dispatch Queue
中,根據追加的順序通過FIFO(先進先出)來執行處理。
有兩種類型的隊列,一種是Serial Dispatch Queue
(串行隊列)等待正在執行中的處理當處理結束時再執行隊列中下一個處理。一種是Concurrent Dispatch Queue
(併發隊列)不等待現在執行中的處理。
Dispatch Queue種類 | 說明 |
---|---|
Serial Dispatch Queue | 等待現在執行中的處理結束 |
Concurrent Dispatch Queue | 不等待現在執行中的處理結束 |
用代碼詳細說明兩種隊列
dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);
當queue
爲Serial Dispatch Queue
時輸出
block0
block1
block2
block3
block4
當queue
爲Concurrent Dispatch Queue
時輸出
block1
block0
block2
block4
block3
Serial Dispatch Queue
當上面的queue
爲Serial Dispatch Queue
時按順序執行,先執行block0
執行結束後執行block1
、block2
依次類推,因爲是串行執行所以系統此時只開闢了一個線程來處理
Concurrent Dispatch Queue
當上面的queue
爲Concurrent Dispatch Queue
時,因爲不用等待執行中的處理結束,所以首先執行block0
,不管block0
是否結束都開始執行後面的block1
,不等block1
執行結束都執行block2
依次類推。以爲是併發執行,實際上是開闢了多個線程同時執行多個處理。
關於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_creat
創建的串行隊列優先級默認變成了最高優先級,實現的效果是當有多個默認優先級的Serial Dispatch Queue
併發執行時,如果設置了某一個Serial 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 Queue
與Concurrent Dispatch Queue
。這個函數第一個參數
是隊列的標識符,標識符的寫法最好按照域名倒寫的方法來表示Dispatch Queue
,這樣方便在調試中查看。第二個參數
表示創建隊列的類型當爲NULL
表示創建串行隊列,DISPATCH_QUEUE_CONCURRENT
表示創建並行隊列。
dispatch_suspend與dispatch_resume
如果再進行某個操作時,不想執行隊列中的操作,在這個操作完成時再執行隊列中的操作,這時用dispatch_suspend
會掛起當前的隊列,此時不會執行隊列中追加的操作。而用dispatch_resume
會恢復掛起的隊列。dispatch_suspend
與dispatch_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_suspend
與dispatch_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);
dispatch_semaphore_wait
用來執行等待操作,當semaphore = 0
時dispatch_semaphore_wait
開始等待阻止其他將要進行的操作。直到接受到信號,因爲接受信號semaphore
自加1所以dispatch_semaphore_wait
取消等待。
注意
當執行dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
這行代碼時,如果semaphore
大於或者等於1,這行代碼會自動將semaphore
減去1。每運行一次semaphore - 1
直到semaphore
爲0。
- dispatch_semaphore_signal
dispatch_semaphore_signal(semaphore);
dispatch_semaphore_signal
會讓semaphore進行加1的操作。
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];
});
本篇博文參考