iOS多線程之GCD
什么是GCD
GCD(grand central dispatch) 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫,為并發代碼在多核硬件(跑 iOS 或 OS X )上執行提供有力支持。它具有以下優點:
- GCD 能通過推遲昂貴計算任務并在后臺運行它們來改善應用的響應性能。
- GCD 提供一個易于使用的并發模型而不僅僅只是鎖和線程,以幫助我們避開并發陷阱。
- GCD 具有在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
相關概念
1. Serial vs. Concurrent 串行 vs. 并發
串行是指同一時間只有一個任務執行,并發是指同一時間可以執行多個任務。
2. Synchronous vs. Asynchronous 同步 vs. 異步
同步是指一個函數等函數體執行完了才返回,異步是指函數立即返回(此時函數體還沒有執行完成)。
3. Critical Section 臨界區
就是一段代碼不能被并發執行,也就是,兩個線程不能同時執行這段代碼。
4. Race Condition 競態條件
程序的輸出結果依賴于不同事件的執行順序。
5. Deadlock 死鎖
兩個(或多個)進程(或線程)都卡住了,等待對方完成或執行某些操作。第一個不能完成是因為它在等待第二個的完成。但第二個也不能完成,因為它在等待第一個的完成。
6. Thread Safe 線程安全
線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你可以在同一時間在多個線程中使用它而不會有問題。另一方面,NSMutableDictionary 就不是線程安全的,應該保證一次只能有一個線程訪問它。
7. Context Switch 上下文切換
一個上下文切換指當你在單個進程里切換執行不同的線程時存儲與恢復執行狀態的過程。這個過程在編寫多任務應用時很普遍,但會帶來一些額外的開銷。
8. Concurrency vs Parallelism 并發與并行
并發是指在同一個時間段內可以有多個任務執行,但是同一時刻只能有一個任務執行。并行是指即使在同一時刻也有多個任務執行。
9. Queues 隊列
GCD 提供有 dispatch queues 來處理代碼塊,這些隊列管理你提供給 GCD 的任務并用 FIFO 順序執行這些任務。這就保證了第一個被添加到隊列里的任務會是隊列中第一個開始的任務,而第二個被添加的任務將第二個開始,如此直到隊列的終點。
10. Serial Queues 串行隊列
串行隊列中的任務一次執行一個,每個任務只在前一個任務完成時才開始。而且,你不知道在一個 Block 結束和下一個開始之間的時間長度,如圖
這些任務的執行時機受到 GCD 的控制;唯一能確保的事情是 GCD 一次只執行一個任務,并且按照我們添加到隊列的順序來執行。
由于在串行隊列中不會有兩個任務并發運行,因此不會出現同時訪問臨界區的風險。
11. Concurrent Queues 并發隊列
在并發隊列中的任務能得到的保證是它們會按照被添加的順序開始執行,但是無法保證任務的開始必須在上一個任務完成之后,也就是說上一個任務還沒完成下一個任務就開始了。任務可能以任意順序完成,你不會知道何時開始運行下一個任務,或者任意時刻有多少 Block 在運行。如圖
何時開始一個 Block 完全取決于 GCD 。如果一個 Block 的執行時間與另一個重疊,也是由 GCD 來決定是否將其運行在另一個不同的核心上,如果那個核心可用,否則就用上下文切換的方式來執行不同的 Block 。
12. Queue Types 隊列類型
首先,系統提供給你一個叫做 主隊列(main queue) 的特殊隊列。和其它串行隊列一樣,這個隊列中的任務一次只能執行一個。然而,它能保證所有的任務都在主線程執行,而主線程是唯一可用于更新 UI 的線程。這個隊列就是用于發生消息給 UIView 或發送通知的。
系統同時提供給你好幾個并發隊列。它們叫做 全局調度隊列(Global Dispatch Queues) 。
最后,你也可以創建自己的串行隊列或并發隊列。這就是說,至少有五個隊列任你處置:主隊列、四個全局調度隊列,再加上任何你自己創建的隊列。
使用
-
dispatch_queue_create
//創建一個串行隊列 dispatch_queue_t serialQue = dispatch_queue_create"serialQueue", DISPATCH_QUEUE_SERIAL); //創建一個并發隊列 dispatch_queue_t conQue = dispatch_queue_create("conCurrentQue", DISPATCH_QUEUE_CONCURRENT); //創建一個高優先級并發隊列 dispatch_queue_attr_t queAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INTERACTIVE, -1); dispatch_queue_create(@"attrQue", queAttr);
-
Main Dispatch Que Or Global Dispatch Que
//獲取主隊列
dispatch_queue_t mainQue = dispatch_get_main_queue();//獲取全局高優先級并發隊列 //優先級 // QOS_CLASS_USER_INTERACTIVE > QOS_CLASS_USER_INITIATED > QOS_CLASS_UTILITY > QOS_CLASS_DEFAULT > QOS_CLASS_BACKGROUND dispatch_queue_t globalQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
-
dispatch_set_target_queue
使用dispatch_queue_create
創建的queue不管是串行的還是并行的,都使用與默認優先級全局隊列相同的優先級。如果要變更生成隊列的優先級,就要使用此函數。生成一個高優先級的并發隊列的代碼入下:
dispatch_queue_t highQue = dispatch_queue_create("highPri", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t gloablHighQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
dispatch_queue_t gloablDefaultQue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);dispatch_set_target_queue(highQue, gloablHighQue); dispatch_async(gloablDefaultQue, ^{ NSLog(@"this is defaulPri queue"); }); dispatch_async(highQue, ^{ NSLog(@"this is highPri queue"); });
運行結果如下:
2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511919 this is highPri queue
2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511909 this is defaulPri queue -
dispatch_after
可以讓指定任務延遲執行,如讓任務延遲3秒執行。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"delayed action"); });
-
Dispatch Group
在追加到隊列中的多個任務全部結束后想執行結束處理。下列代碼展示了當幾個任務全部結束后才做最后處理的情況。
NSLog(@"1 + 2 + 3 + ... + 100 = ");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_queue_t mainQ = dispatch_get_main_queue();
__block NSInteger sum1 = 0;
__block NSInteger sum2 = 0;
__block NSInteger sum3 = 0;
__block NSInteger sum4 = 0;
__block NSInteger sum5 = 0;
__block NSInteger sum6 = 0;
__block NSInteger sum7 = 0;
__block NSInteger sum8 = 0;
__block NSInteger sum9 = 0;
__block NSInteger sum10 = 0;
__block NSInteger sumTotal = 0;dispatch_group_async(group, queue, ^{ for (NSInteger j = 1; j < 11; j ++) { sum1 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 11; j < 21; j ++) { sum2 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 21; j < 31; j ++) { sum3 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 31; j < 41; j ++) { sum4 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 41; j < 51; j ++) { sum5 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 51; j < 61; j ++) { sum6 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 61; j < 71; j ++) { sum7 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 71; j < 81; j ++) { sum8 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 81; j < 91; j ++) { sum9 += j; } }); dispatch_group_async(group, queue, ^{ for (NSInteger j = 91; j < 101; j ++) { sum10 += j; } }); dispatch_group_notify(group, mainQ, ^{ sumTotal = sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7 + sum8 + sum9 + sum10; NSLog(@"%li", sumTotal); });
-
dispatch_barrier_async
在訪問數據庫或文件時,使用串行隊列可避免數據競爭的問題。寫入處理確實不可與寫入處理以及讀取處理并行處理,但是如果讀取處理與讀取處理并行,也不會出問題,為了高效進行訪問,讀取處理追加到并行隊列,寫入處理在沒有讀取處理進行的情況下追加到串行隊列。這時候可以使用此函數。
示例代碼如下:
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
for (NSInteger i = 0; i < 10; i ++) {dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"reader... %li", i); }); } dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"writer..."); }); for (NSInteger i = 10; i < 20; i ++) { dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"reader...%li", i); }); } NSLog(@"---------------");
dispatch_suspend
dispatch_resume
當追加大量處理到隊列時,在追加處理的過程中,有時希望不執行已追加的處理。這種情況下,只要掛起隊列即可,當可以執行再恢復。
dispatch_suspend(queue);
dispatch_resume(queue);
這倆函數對已經執行的處理沒有影響,掛起后,追加到隊列但尚未運行的處理會停止執行。而恢復則使得這些處理能夠繼續執行。-
dispatch_semaphore
當并行的處理更新數據時,會產生數據不一樣的情況,有時應用程序還會異常結束,此函數可以用來控制線程互斥訪問共享資源。如下示例代碼
NSMutableArray *arrayM = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);for (NSInteger i = 0; i < 1000; i ++) { dispatch_async(queue, ^{ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); [arrayM addObject:@(i)]; dispatch_semaphore_signal(sem); }); } }
-
dispatch_once
該函數是保證在應用程序中只執行一次制定處理的。
static dispatch_once_t once;for (NSInteger i = 0 ; i < 10 ; i ++) { dispatch_once(&once, ^{ NSLog(@"print only 1 time"); }); }