1.基本介紹
什么是GCD
Grand Central Dispatch (GCD) 是異步執行任務的技術之一。開發者只需要定義想執行的任務并追加到適當的Dispatch Queue中,GCD就能生成必要的線程并計劃執行任務。
GCD的優勢
- GCD會自動的利用多核(比如雙核,四核)
- GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- GCD會自動根據系統負載來增減線程數量
Dispatch Queues
GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它可以接受任務,并將任務以先到先執行的順序來執行。dispatch queue可以是并發的或串行的。并發任務會像NSOperationQueue那樣基于系統負載來合適地并發進行,串行隊列同一時間只執行單一任務。
- 串行隊列:按FIFO每次取出一個任務執行,當前一個任務完成后才會取出第二個任務。
-
并發隊列:按FIFO取出任務執行,但是不會等待前一個任務完成就會取出第二個任務。
SerialQueue.png
任務
- 同步任務:同步執行,會阻塞當前線程,直到當前的block任務執行完畢。
- 異步任務:異步執行,不會阻塞當前線程。
隊列與任務的組合情況
同步任務 | 異步任務 | |
---|---|---|
主隊列 | 在主隊列添加同步任務會死鎖 | 不開啟新線程,在主線程按序執行任務 |
串行隊列 | 不開啟新線程,在當前線程按序執行任務 | 開啟一條線程,在這個線程中按序執行任務 |
并發隊列/全局并發隊列 | 不開啟新線程,在當前線程按序執行任務 | GCD根據系統資源開啟多條線程執行任務 |
小結
- 同步和異步決定了是否開啟新的線程。main隊列除外,在main隊列中,同步或者異步執行都不會另開線程。
- 串行和并行,決定了任務是否同時執行。
- 不要在執行串行隊列的線程中向當前的串行隊列添加同步任務,會導致死鎖。
2.GCD使用
獲取Dispatch Queue的方法有兩種。
第一種方法是通過GCD的API生成Dispatch Queue。
dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
第一個參數指定該隊列的名稱,該名稱會出現應用程序崩潰時產生的CrashLog中,在調用過程中我們也可以使用dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
獲取當前隊列的名字。
第二個參數指定該隊列的類型。
DISPATCH_QUEUE_SERIAL
或者NULL表示該隊列是串行隊列,
DISPATCH_QUEUE_CONCURRENT
表示該隊列是并發隊列。
第二種方法是獲取系統標準提供的Dispatch Queue。
系統會給我們提供Main Dispatch Queue(主隊列)和Global Dispatch Queue(全局并發隊列)。
Main Dispatch Queue
Main Dispatch Queue顧名思義,是在主線程中執行的隊列。因為主線程只有一個,所以主隊列自然就是串行隊列。追加到主隊列的處理在主線程的RunLoop中執行,因此要將用戶界面的更新等一些必須在主線程中執行的處理追加到主隊列中使用。
// Main 隊列獲取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
在上面說過,同步會阻塞當前線程,執行完block里面任務才會繼續往下走。dispatch_sync阻塞了主線程,然后把任務追到加主隊列,并在主線程執行,但是此時的主線程已經被阻塞,所以block任務也無法執行,block任務不執行,dispatch_sync會繼續阻塞主線程。這樣子就產生了死鎖。
Global Dispatch Queue
Global Dispatch Queue是全局并發隊列,沒有必要通過dispatch_queue_create函數逐個生成并發,只要獲取全局并發隊列使用即可。
全局并發隊列有4個執行優先級,分別是高優先級(High Priority),默認優先級(Default Priority),低優先級(Low Priority)和后臺優先級(Background Priority)。
// 獲取高優先級
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// 獲取默認優先級
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取低優先級
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 獲取后臺優先級
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue
dispatch_queue_create函數生成的隊列不管是串行隊列還是并發隊列,都使用跟默認優先級的全局并發隊列相同的優先級。而設置一個隊列的優先級可以使用dispatch_set_target_queue。
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", NULL);
dispatch_queue_t backgroundGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialQueue, backgroundGlobalQueue);
指定要變更優先級的隊列為第一個參數,指定參考隊列為第二個參數。
dispatch_set_target_queue
函數還可以改變隊列的執行層次。在多個串行隊列中,使用dispatch_set_target_queue
函數指定目標為某一串行隊列,那么原本應并行執行的多個串行隊列,在目標串行隊列上只能同時執行一個任務。在必須要將不可并發執行的處理追加到多個串行隊列中時,可以使用dispatch_set_target_queue
函數防止并發執行。
dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("serialQueue2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("serialQueue3", NULL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("serialQueue4", NULL);
dispatch_set_target_queue(serialQueue2, serialQueue1);
dispatch_set_target_queue(serialQueue3, serialQueue1);
dispatch_set_target_queue(serialQueue4, serialQueue1);
dispatch_async(serialQueue2, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
dispatch_async(serialQueue3, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
dispatch_async(serialQueue4, ^{
NSLog(@"%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
//輸出結果為
serialQueue2
serialQueue3
serialQueue4
dispatch_after
想在指定時間后執行的情況,可使用dispatch_after
函數來實現
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"等待3秒");
});
使用dispatch_after
需要注意的是,這個函數并不是在指定時間后執行,而是在指定時間把任務追加到指定的隊列中。如上面的代碼,3秒后只是把任務追加到主隊列當中,具體執行時間與主線程擁塞程度有關。
Dispatch Group
我們經常會有這樣的需求,在多個追加到Dispatch Queue中的處理執行完畢后,進行一些操作。在使用串行隊列的時候,我們只需要在最后追加結束后的處理。但是在使用多個并發隊列或同時使用多個隊列時,就需要使用Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
//執行結果
block1
block3
block2
done
很明顯,這種方式是不阻塞的。由于我們是異步把任務添加到隊列中,所以任務執行的順序是不一定的。但是dispatch_group_notify里面的block肯定是最后執行。
如果想要阻塞線程可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第二個參數為等待時間DISPATCH_TIME_FOREVER
表示永遠等待。
當任務中有completion block
時,這種任務是馬上完成的,例如網絡請求。但是我們想讓任務在收到completion block
時才完成,這時需要我們手動管理任務的開始和結束。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[Request requestWithSuccess:^{
NSLog(@"success");
dispatch_group_leave(group);
} failBlk:^{
NSLog(@"fail");
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[Request requestWithSuccess:^{
NSLog(@"success");
dispatch_group_leave(group);
} failBlk:^{
NSLog(@"fail");
dispatch_group_leave(group);
}];
dispatch_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部完成");
});
通過dispatch_group_enter
,dispatch_group_leave
兩個函數可以實現進入,退出兩個動作。要注意enter
和leave
要成對出現,否則group永遠不會結束。
dispatch_barrier_async
在訪問數據時,使用串行隊列可以避免數據競爭問題。寫入處理不可與其他的寫入處理以及包含讀取處理的其他某些處理并行執行,但是讀取處理只和讀取處理并行執行,那么多個并行執行就不會發生問題。也就是說,為了高效率的訪問,需要實現多讀單寫。
GCD為我們提供了一種方便的實現dispatch_barrier_async
,它等待所有位于barrier函數之前的操作執行完畢后執行,并且在barrier函數執行完成后,barrier函數之后的操作才會得到執行,該函數需要同dispatch_queue_create函數生成的并發隊列一起使用。
dispatch_queue_t queue = dispatch_queue_create("queueForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務1");
});
dispatch_async(queue, ^{
NSLog(@"任務2");
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"任務3 barrier");
});
dispatch_async(queue, ^{
NSLog(@"任務4");
});
dispatch_async(queue, ^{
NSLog(@"任務5");
});
//輸出結果
任務2
任務1
任務3 barrier
任務4
任務5
dispatch_apply
dispatch_apply
函數按指定的次數降block追加到指定的隊列中,并阻塞線程等待全部處理執行結束。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3"];
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
NSLog(@"完成");
//輸出結果
2
1
3
完成
由于dispatch_apply
函數會等待所有處理執行結束,所以最好在dispatch_async
函數中非同步的執行dispatch_apply
函數
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3"];
dispatch_async(queue, ^{
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程
});
});
dispatch_suspend/dispatch_resume
//掛起指定的隊列
dispatch_suspend(queue);
//恢復指定的隊列
dispatch_resume(queue);
dispatch_suspend
函數對已經執行的處理沒有影響,隊列中未執行的處理會被掛起,dispatch_resume
函數會恢復這些處理的執行。
Dispatch Semaphore
信號量可以控制同時訪問資源的數量,解決資源爭奪的問題。信號量持有計數,計數為0等待,計數大于或等于1時,減去1而不等待。類似于過安檢時,允許n個人一起安檢,每當一個人安檢完成,則下一個人可以進行安檢。
//創建計數值為1的信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSArray *array = @[@"1",@"2",@"3",@"4",@"5"];
for (NSString *string in array) {
dispatch_async(queue, ^{
//dispatch_semaphore_wait函數等待信號量的計數值大于或等于1時,對計數減1并向下執行,否則等待。
//DISPATCH_TIME_FOREVER表示永遠等待。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1
NSLog(@"%@",string);
//dispatch_semaphore_signal函數將信號量的計數加1
dispatch_semaphore_signal(semaphore);
});
}
上面代碼創建了一個計數初始值為1的信號量,然后向全局并發隊列中添加了5個任務,當有一個任務通過dispatch_semaphore_wait
函數時,信號量的計數被減1,此時計數為0,剩余的任務則會等待,直到dispatch_semaphore_signal
將計數加1。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
//在等待時間內計數值達到大于等于1,減1繼續處理
}
else{
//在等待時間內計數值為0
}
我們也可以通過dispatch_semaphore_wait
函數的返回值進行分支處理。當我們給定一個等待時間,如果信號量的計數值在等待時間內達到大于等于1,dispatch_semaphore_wait
返回0,如果超過這個時間計數值還為0,則返回值不為0。
dispatch_once
dispatch_once
函數保證在應用程序執行中只執行一次處理。并且在多線程環境下能保證安全。
@implementation Manager
+ (Manager *)sharedInstance
{
static Manage *manager = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
manager = [[Manager alloc] init];
});
return manager;
}
我們一般用來創建單例。
Dispatch Source
Dispatch Source的種類
名稱 | 種類 |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 自定義事件,變量增加 |
DISPATCH_SOURCE_TYPE_DATA_OR | 自定義事件,變量OR |
DISPATCH_SOURCE_TYPE_MACH_SEND | MACH 端口發送 |
DISPATCH_SOURCE_TYPE_MACH_RECV | MACH 端口接收 |
DISPATCH_SOURCE_TYPE_PROC | 檢測到與進程相關的事件 |
DISPATCH_SOURCE_TYPE_READ | IO操作,如對文件的操作、socket操作的讀響應 |
DISPATCH_SOURCE_TYPE_SIGNAL | 接受信號 |
DISPATCH_SOURCE_TYPE_TIMER | 定時器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件系統有變更 |
DISPATCH_SOURCE_TYPE_WRITE | IO操作,如對文件的操作、socket操作的寫響應 |
使用DISPATCH_SOURCE_TYPE_TIMER的定時器的例子
//倒計時時間
__block int timeout = 60;
//創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//創建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設置開始時間未當前時間,間隔1秒,誤差為0
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC), 0);
//觸發事件
dispatch_source_set_event_handler(timer, ^{
if (timeout <= 0) {
//取消dispatch source
dispatch_source_cancel(timer);
}
else{
timeout --;
NSLog(@"%d",timeout);
}
});
//啟動定時器
dispatch_resume(timer);