1 簡介
1.1 功能
Grand Central Dispatch(GCD)技術讓任務並行排隊執行,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。任務可以是一個函數(function)或者是一個block。 GCD的底層依然是用線程實現,不過這樣可以讓程序員不用關注實現的細節。
GCD中的隊列稱為dispatch queue,它可以保證先進來的任務先得到執行通過它能夠大大簡化多線程編程。工程師只要將要執行的任務(執行代碼塊)放入隊列中,GCD將會為需要執行的任務創建thread,從而放入dispatch queue中,當將任務添加到隊列立即安排開始執行。
-
任務:其是並發程序的執行單位;
-
隊列:其是並發程序的管理單位。
1.2 第一個程序
1) Object-C 語言
2 // 創建隊列
3 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
4 // 添加block任務
5 dispatch_async(queue, ^{
6 printf( " hello world\n ");
7 });
8 sleep( 2); // 若不休眠2秒,主線程就會退出,導致dispatch queue線程也退出。
9 return 0;
10 }
11 輸出:
12 hello world
2) Swift 語言
2 super.viewDidLoad()
3 let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) // 創建隊列
4 dispatch_async(queue){ // 添加block任務
5 print( " hello world\n ");
6 }
7 }
8 輸出:
9 hello world
2 設計與概念
2.1 Queue類型
Dispatch queue的實現機制是基於C實現的,並且GCD自動為用戶提供了一些dispatch queue,當然也可以自定義一些queue。其中queue類型只有三種:Serial、Concurrent和Main dispatch queue。
表 1 dispatch queue類型
Type |
Description |
Serial(串行) |
又稱為private dispatch queues,同一時刻只執行一個任務,並按添加到serial的順序執行。當創建多個Serial queue時,雖然它們各自是同步執行的,但Serial queue與Serial queue之間是並發執行的。Serial queue通常用於同步訪問特定的資源或數據。 |
Concurrent(並行) |
又稱為global dispatch queue,同一時刻可執行多個任務,任務開始執行的順序按添加的順序執行,但是執行完成的順序是隨機的,同時可以創建執行的任務數量依賴系統條件。 |
Main dispatch queue(主隊列) |
它是全局可用的serial queue,它是在應用程序主線程上執行任務的。 |
2.2 Queue相關技術
除了dispatch queue外,GCD還提供了一些技術來輔助queue管理代碼。
表 2 相關技術
Type |
Description |
Dispatch groups |
Dispatch group是用來監控一組block對象的完成情況,可以根據需要對block對象進行異步或同步操作。 |
Dispatch semaphores |
Dispatch semaphores非常類似傳統的信號量,但效率更高。 |
Dispatch sources |
Dispatch source是為了響應指定系統事件,而產生的消息。當一個事件發生,那么dispatch source將提交你的異步任務代碼到指定的dispatch queue線程中。 |
3 創建隊列
dispatch queue的隊列有serial、concurrent和main三種,如下分別介紹如何獲得這 三種隊列。
3.1 並行隊列
由於系統已經為每個應用程序創建了四個不同優先級的Concurrent dispatch queue,用戶不需要創建Concurrent dispatch queue,只需通過dispatch_get_global_queue 函數獲得就可以,其聲明如下:
dispatch_queue_t dispatch_get_global_queue(long identifier,long flags); |
|
Ps:
雖然dispatch queue是reference-counted對象,但是由於是全局對象,所以不需要手動進行retain 和 release。
3.2 串行隊列
不像Concurrent dispatch queue有全局的隊列,Serial Dispatch Queue需要用戶手動進行創建和管理。其中創建的函數是dispatch_queue_create,其聲明如下:
dispatch_queue_t dispatch_queue_create(const char *label dispatch_queue_attr_t attr); |
|
3.3 主隊列
除了需要用戶自定義serial dispatch queue外,系統還為用戶創建了一個serial queue並將其綁定到應有程序的main thread中。用戶可以通過dispatch_get_main_queue直接獲得。其聲明如下:
dispatch_queue_t dispatch_get_main_queue(void); |
3.4 queue context
所有的dispatch 對象(包括dispatch queue)都允許指定一個自定義結構的context(上下文)對象,可以通過dispatch_set_context和dispatch_get_context函數,設置和獲得這個context對象,系統不會使用這個對象,其只是負責傳遞這個對象,所有用戶需要自己進行創建和釋放。
對於dispatch queue,可以在context結構中存放一個指向Objective-C或標量結構的指針,從而可以在queue代碼中使用這個指針,最后可以在queue的清理函數中釋放這個指針,具體例子可以參考1.3.5節。
3.5 finalizer function
在創建了一個serial dispatch queue后,可以手動設置一個清理函數(finalizer function)給queue,從而當queue退出時能夠調用該清理函數。其中dispatch queue也是一個Objective-C對象,所以其也擁有一個引用計數,但dispatch queue的引用計數為0時,會被系統回收,在回收之前會調用指定的清理函數。可以通過dispatch_set_finalizer_f函數來設置清理函數。
如下的例子為配置了一個queue context對象,並在定制的清理函數中釋放這個queue context指針。
2 {
3 MyDataContext* theData = (MyDataContext*)context;
4 myCleanUpDataContextFunction(theData); // Clean up the contents of the structure
5 free(theData); // Now release the structure itself.
6 }
7 dispatch_queue_t createMyQueue()
8 {
9 MyDataContext* data = (MyDataContext*) malloc( sizeof(MyDataContext));
10 myInitializeDataContextFunction(data);
11 // Create the queue and set the context data.
12 dispatch_queue_t serialQueue = dispatch_queue_create( " com.example.CriticalTaskQueue ", NULL);
13 if (serialQueue)
14 {
15 dispatch_set_context(serialQueue, data);
16 dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
17 }
18 return serialQueue;
19 }
4 添加任務
為了執行任務,用戶必須將任務添加(dispatch)到合適的dispatch queue中。可以將任務以異步或同步的方式執行,同時可以單獨將任務添加到queue,或者是將多個任務組成group。一旦任務被添加到queue中,queue將盡可能完成任務的執行。
4.1 添加single任務
添加single任務是指將任務單獨添加到queue中,可以知道queue中的任務是按添加的順序開始執行,但無法確定任務何時被執行。GCD有兩種方式將任務添加到queue中:同步和異步。
1) 異步:dispatch_async函數
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); |
該函數將block或函數添加到queue中,且添加后立即返回,不需要等待block或函數被執行完成。其中由queue的類型來決定同一個dispatch queue是串行還是並行執行,而不同的dispatch queue則都是並行執行的。
2) 同步:dispatch_syn函數
void dispatch_sync( dispatch_queue_t queue, dispatch_block_t block); |
該函數將block或函數添加到dispatch queue后,不會立即返回,必須等待block執行完成才能返回。串行或並行同樣受queue的類型決定。
4.2 添加任務到主線程
由於dispatch queue本身是線程安全的,而UIKit則非線程安全類型。所以從后台線程向任何GUI對象發送消息都是不可能的,既在dispatch queue中的任務無法對GUI的對象進行修改。然而有一種解決辦法:可以通過dispatch_get_main_queue函數獲得主線程隊列,接着將需要訪問的任務發布到主線程隊列中。如swift實現:
2 dispatch_async(queue){
3 dispatch_async(dispatch_get_main_queue())
4 {
5 self.TextView.text = resultSummary; // 訪問UI中的TextView控件
6 }
7 }
5 其它技術
5.1 等待group任務完成
dispatch group是隊列的一種組合,其將多個dispatch queue組合為一個組,通過group使得某個block等待其它線程完成后,該block才得以執行。其中相關的函數有:
-
dispatch_group_create
通過該函數創建一個dispatch group,
-
dispatch_group_async(group, queue, block)
其語義與dispatch_async類似,都是將任務添加到queue中。該函數不同的是將queue的任務組合到group中。
-
dispatch_group_notify(group, queue, block)
該函數是指等待group中的任務都完成后,才執行block任務。其中這里的queue與dispatch_group_async的queue是不相關的,可以不同。
如object-C 語言的例子:
2 dispatch_group_t group = dispatch_group_create();
3 dispatch_group_async(group, queue, ^{
4 [NSThread sleepForTimeInterval: 1];
5 NSLog( @" group1 ");
6 });
7 dispatch_group_async(group, queue, ^{
8 [NSThread sleepForTimeInterval: 2];
9 NSLog( @" group2 ");
10 });
11 dispatch_group_async(group, queue, ^{
12 [NSThread sleepForTimeInterval: 3];
13 NSLog( @" group3 ");
14 });
15 dispatch_group_notify(group, dispatch_get_main_queue(), ^{
16 NSLog( @" updateUi ");
17 });
18 dispatch_release(group);
5.2 任務同步操作
dispatch_barrier_async是一種同步操作,在其前面的任務執行結束后它才執行,而且其后面的任務等它執行完成之后才會執行。
如object-C實現:
2 dispatch_async(queue, ^{
3 [NSThread sleepForTimeInterval: 2];
4 NSLog( @" dispatch_async1 ");
5 });
6 dispatch_async(queue, ^{
7 [NSThread sleepForTimeInterval: 4];
8 NSLog( @" dispatch_async2 ");
9 });
10 dispatch_barrier_async(queue, ^{
11 NSLog( @" dispatch_barrier_async ");
12 [NSThread sleepForTimeInterval: 4];
13 });
14 dispatch_async(queue, ^{
15 [NSThread sleepForTimeInterval: 1];
16 NSLog( @" dispatch_async3 ");
17 });
18 輸出為:
19 dispatch_async1
20 dispatch_async2
21 dispatch_barrier_async
22 dispatch_async3
5.3 循環迭代
通過dispatch_apply可以執行某段代碼塊n次,從而來替換循環(while或for)操作。
如一般情況下的循環語句為:
2 printf( " %u\n ",i);
3 }
通過dispatch queue操作為:
2 dispatch_apply(count, queue, ^(size_t i) {
3 printf( " %u\n ",i);
4 });
6 參考文獻
-
Apple:Concurrency Programming Guide
-
Apple:Grand Central Dispatch (GCD) Reference