什么是 GCD
GCD 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫,為并發代碼在多核硬件(跑 iOS 或 OS X )上執行提供有力支持。它具有以下優點:
- GCD 能通過推遲昂貴計算任務并在后臺運行它們來改善你的應用的響應性能。
- GCD 提供一個易于使用的并發模型而不僅僅只是鎖和線程,以幫助我們避開并發陷阱。
- GCD 具有在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
術語
首先,我們先來了解一下在 iOS 并發編程中非常重要的三個術語,這是我們理解 iOS 并發編程的基礎:
- 進程(process),指的是一個正在運行中的可執行文件。每一個進程都擁有獨立的虛擬內存空間和系統資源,包括端口權限等,且至少包含一個主線程和任意數量的輔助線程。另外,當一個進程的主線程退出時,這個進程就結束了;
- 線程(thread),指的是一個獨立的代碼執行路徑,也就是說線程是代碼執行路徑的最小分支。在 iOS 中,線程的底層實現是基于 POSIX threads API 的,也就是我們常說的 pthreads ;
- 任務(task),指的是我們需要執行的工作,是一個抽象的概念,用通俗的話說,就是一段代碼。
串行 vs. 并發
從本質上來說,串行和并發的主要區別在于允許同時執行的任務數量。串行,指的是一次只能執行一個任務,必須等一個任務執行完成后才能執行下一個任務;并發,則指的是允許多個任務同時執行。
同步 vs. 異步
同樣的,同步和異步操作的主要區別在于是否等待操作執行完成,亦即是否阻塞當前線程。同步操作會等待操作執行完成后再繼續執行接下來的代碼,而異步操作則恰好相反,它會在調用后立即返回,不會等待操作的執行結果。
隊列 vs. 線程
在 iOS 中,有兩種不同類型的隊列,分別是串行隊列和并發隊列。正如我們上面所說的,串行隊列一次只能執行一個任務,而并發隊列則可以允許多個任務同時執行。iOS 系統就是使用這些隊列來進行任務調度的,它會根據調度任務的需要和系統當前的負載情況動態地創建和銷毀線程,而不需要我們手動地管理。
Operation Queues vs. Grand Central Dispatch (GCD)
簡單來說,GCD 是蘋果基于 C 語言開發的,一個用于多核編程的解決方案,主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統。而 Operation Queues 則是一個建立在 GCD 的基礎之上的,面向對象的解決方案。它使用起來比 GCD 更加靈活,功能也更加強大。下面簡單地介紹了 Operation Queues 和 GCD 各自的使用場景:
Operation Queues :相對 GCD 來說,使用 Operation Queues 會增加一點點額外的開銷,但是我們卻換來了非常強大的靈活性和功能,我們可以給 operation 之間添加依賴關系、取消一個正在執行的 operation 、暫停和恢復 operation queue 等;
GCD :則是一種更輕量級的,以 FIFO 的順序執行并發任務的方式,使用 GCD 時我們并不關心任務的調度情況,而讓系統幫我們自動處理。但是 GCD 的短板也是非常明顯的,比如我們想要給任務之間添加依賴關系、取消或者暫停一個正在執行的任務時就會變得非常棘手。
GCD和RunLoop的關系
在每個RunLoop周期醒來之后,會獲取GCD在Main queue上的任務并執行,2者協同配合。
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"我會在RunLoop醒來之后被RunLoop獲取并且執行");
});
最簡單的一個demo
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
///block塊
});
GCD使用方式可以簡化成這個格式:
dispatch_async(queue,block);
queue:是代碼執行的隊列
block:交給GCD處理的block
dispatch_queu_t (隊列)
隊列中分為2個種隊列:Serial(串行),Concurrent(并行)
- Serial:同時只執行一個任務。
- Concurrent:可以并發的執行多個任務,但執行完成順序是隨機的。
系統提供6個全局并發隊列,分別是一個主隊列,和5個優先級不同的調度隊列,除主線程隊列是串行,其余都是并行隊列,優先級越高,越優先執行
- Main queue:主線程隊列,在此隊列執行將會在主線程調用,串行.
- 優先級為QOS_CLASS_USER_INTERACTIVE的調度隊列:user interactive等級表示任務需要被立即執行提供好的體驗。
- 優先級為QOS_CLASS_USER_INITIATED的調度隊列:user initiated等級表示任務由UI發起異步執行。適用場景是需要及時結果同時又可以繼續交互的時候。
- 優先級為QOS_CLASS_USER_DEFAULT的調度隊列:默認優先級。
- 優先級為QOS_CLASS_USER_UTILITY的調度隊列:utility等級表示需要長時間運行的任務。
- 優先級為QOS_CLASS_USER_BACKGROUND的調度隊列:background等級表示用戶不會察覺的任務,使用它來處理預加載,或者不需要用戶交互和對時間不敏感的任務。
獲取不同級別隊列的代碼
///獲取主線程隊列
dispatch_get_main_queue();
///QOS_CLASS_USER_INTERACTIVE 隊列
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
///QOS_CLASS_USER_INITIATED 隊列
dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
///QOS_CLASS_DEFAULT 隊列
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
///QOS_CLASS_UTILITY 隊列
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
///QOS_CLASS_UTILITY 隊列
dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
第二個參數總是0,預留選項
當然你也可以用DISPATCH_QUEUE_PRIORITY
枚舉來獲取不同優先級的隊列
映射關系:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
創建自己的隊列
當系統隊列不滿足需求的時候我們也可以自己創建隊列。
///創建串行隊列,名字叫com.SERIAL
dispatch_queue_create("com.SERIAL", DISPATCH_QUEUE_SERIAL);
///創建并行隊列,名字叫com.CONCURRENT
dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
也可以為自定義隊列設置優先級
方法一:
///創建一個隊列的配置,分別是串行隊列,優先級為比QOS_CLASS_DEFAULT少1的優先級
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1);
dispatch_queue_t queue = dispatch_queue_create("com.custom.queue_attr", attr);
注意:dispatch_queue_attr_make_with_qos_class
最后一個參數必須是小于等于0的值,如果傳給大于0,或者小于QOS_MIN_RELATIVE_PRIORITY的值,將會返回NULL,附文檔上的說明
A relative priority within the QOS class. This value is a negative
offset from the maximum supported scheduler priority for the given class.
Passing a value greater than zero or less than QOS_MIN_RELATIVE_PRIORITY
results in NULL being returned.
方法二:
利用參考隊列來設置優先級
///創建一個需要被設置優先級的隊列
dispatch_queue_t queue = dispatch_queue_create("com.target",NULL);
///取一個參考優先級的隊列
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
///將參考的優先級賦值于隊列
dispatch_set_target_queue(queue, referQueue);
async和sync(異步調用和同步調用)
///異步調用
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
///同步調用
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- queue參數是block執行所在的隊列,串行隊列或者并行隊列
- block參數是執行的block塊
異步調用會立即返回,而同步調用會阻塞當前線程,等待block執行完畢才會繼續。
一些簡單的demo
dispatch_queue_t queueConcurrent = dispatch_queue_create("DISPATCH_QUEUE_CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queueConcurrent, ^{
///todo
});
dispatch_async(queueConcurrent, ^{
///todo
});
注意串行隊列的死鎖問題:由于dispatch_sync需要等待block被執行,這就非常容易發生死鎖。如果一個串行隊列,使用dispatch_sync提交block到自己隊列中,就會發生死鎖,如下代碼
dispatch_queue_t queueSerial = dispatch_queue_create("DISPATCH_QUEUE_SERIAL", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueSerial, ^{
dispatch_sync(queueSerial, ^{
///發生死鎖
NSLog(@"永遠執行不到");
});
});
解釋:dispatch_sync需要等待block執行完成,同時由于隊列串行,block的執行需要等待前面的任務,也就是dispatch_sync執行完成。兩者互相等待,永遠也不會執行完成,死鎖就這樣發生了。
dispatch_once(只執行一次)
在APP生命周期之中只執行一次,使用實現單列的場景
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
///只執行一次
});
dispatch_after(延后提交Block)
延后一段時間來提交block代碼,是提交不是執行,只是在一段時間之后來提交這個block給GCD,而不能保證立即執行,具體看queue是否繁忙,使用于延后處理的事務,如彈出好評框。
///推遲1秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
///推遲提交的block
});
dispatch_apply(迭代)
把一項任務提交到隊列中多次執行,具體是并行執行還是串行執行由隊列本身決定.注意,dispatch_apply不會立刻返回,在執行完畢后才會返回,是同步的調用。
適用場景,遍歷一個數組對內部元素做迭代。
///遍歷10次
dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
///遍歷執行的代碼
});
dispatch_barrier_async(執行的時候獨占隊列)
使用dispatch_barrier將任務加入到并行隊列之后,任務會在前面任務全部執行完成之后執行,任務執行過程中,其他任務無法執行,直到barrier任務執行完
最典型的使用場景是多線程讀寫問題
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-pre-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-pre-2");
});
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4");
});
輸出
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-pre-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-pre-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-barrier
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915599] dispatch-3
2016-03-05 02:32:02.867 GCDDemo[2422:1915614] dispatch-4
Dispatch_groups(監控多個異步任務)
dispatch groups
是專門用來監視多個異步任務。dispatch_group_t實例用來追蹤不同隊列中的不同任務。
當group里所有事件都完成 API有兩種方式發送通知,第一種是dispatch_group_wait
,會阻塞當前進程,等所有任務都完成或等待超時。第二種方法是使用dispatch_group_notify
,異步執行閉包,不會阻塞。
適用場景:多個網絡請求同時發出,全部完成回調處理
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"1");
sleep(1);
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
sleep(2);
});
//阻塞
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//異步
dispatch_group_notify(group, concurrentQueue, ^{
NSLog(@"所有任務完成");
});
dispatch_time_t (GCD定時器)
通過dispatch_time_t,我們可以用GCD做定時器
執行一次
///執行一次(利用dispatch_after)
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^(void){
//執行事件
});
重復執行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL,0), 1 * NSEC_PER_SEC, 0); //每秒執行
dispatch_source_set_event_handler(_timer, ^{
//在這里執行事件
});
dispatch_resume(_timer);