本篇博客共分以下幾個模塊來介紹GCD的相關內容:
多線程相關概念多線程編程技術的優缺點比較?GCD中的三種隊列類型The main queue(主線程串行隊列)Global queue(全局並發隊列)Custom queue (自定義隊列)Group queue (隊列組)GCD中一些系統提供的常用dispatch方法GCD 全稱Grand Center Dispatch (抽象層次:高)
優點:是 Apple 開發的一個多核編程的解決方法,簡單易用,效率高,速度快,基於C語言,更底層更高效,並且不是Cocoa框架的一部分,自動管理線程生命周期(創建線程、調度任務、銷毀線程)。缺點: 使用GCD的場景如果很復雜,就有非常大的可能遇到死鎖問題。
GCD抽象層次最高,使用也簡單,因此,蘋果也推薦使用GCD
GCD編程的核心就是dispatch隊列,dispatch block的執行最終都會放進某個隊列中去進行。
The main queue(主線程串行隊列): 與主線程功能相同,提交至Main queue的任務會在主線程中執行, Main queue 可以通過dispatch_get_main_queue()來獲取。 Global queue(全局並發隊列): 全局並發隊列由整個進程共享,有高、中(默認)、低、後台四個優先級別。 Global queue 可以通過調用dispatch_get_global_queue函數來獲取(可以設置優先級) Custom queue (自定義隊列): 可以為串行,也可以為並發。 Custom queue 可以通過dispatch_queue_create()來獲取; Group queue (隊列組):將多線程進行分組,最大的好處是可獲知所有線程的完成情況。 Group queue 可以通過調用dispatch_group_create()來獲取,通過dispatch_group_notify,可以直接監聽組裡所有線程完成情況。gcd中相關函數的使用一般都是以dispatch開頭
dispatch_sync 同步執行任務函數,不會開啟新的線程,dispatch_async 異步執行任務函數,會開啟新的線程
獲取主線程串行隊列dispatch_queue_t mainQueue = dispatch_get_main_queue();主線程串行隊列同步執行任務,在主線程運行時,會產生死鎖
dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_sync(mainQueue,^{ NSLog("MainQueue"); });程序一直處於等待狀態,block中的代碼將執行不到主線程串行隊列異步執行任務,在主線程運行,不會產生死鎖。
dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(mainQueue,^{ NSLog("MainQueue"); });程序正常運行,block中的代碼正常運行從子線程,異步返回主線程更新UI<這種使用方式比較多>
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalQueue, ^{ //子線程異步執行下載任務,防止主線程卡頓 NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"]; NSError *error; NSString *htmlData = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; if (htmlData != nil) { dispatch_queue_t mainQueue = dispatch_get_main_queue(); //異步返回主線程,根據獲取的數據,更新UI dispatch_async(mainQueue, ^{ NSLog(@"根據更新UI界面"); }); } else { NSLog(@"error when download:%@",error); } });主線程串行隊列由系統默認生成的,所以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。
耗時的操作,比如讀取網絡數據,IO,數據庫讀寫等,我們會在另外一個線程中處理這些操作,然後通知主線程更新界面
獲取全局並發隊列
//程序默認的隊列級別,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //HIGH dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); //LOW dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //BACKGROUND dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);全局並發隊列同步執行任務,在主線程執行會導致頁面卡頓。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_sync(globalQueue, ^{ sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");控制台輸出如下:
2015-11-18 15:51:45.550 Whisper[33152:345023] current task 2015-11-18 15:51:47.552 Whisper[33152:345023] sleep 2.0s 2015-11-18 15:51:47.552 Whisper[33152:345023] next task2s鐘之後,才會執行block代碼段下面的代碼。全局並發隊列異步執行任務,在主線程運行,會開啟新的子線程去執行任務,頁面不會卡頓。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");控制台輸出如下:
2015-11-18 15:50:14.999 Whisper[33073:343781] current task 2015-11-18 15:50:15.000 Whisper[33073:343781] next task 2015-11-18 15:50:17.004 Whisper[33073:343841] sleep 2.0s主線程不用等待2s鐘,繼續執行block代碼段後面的代碼。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ NSLog(@"最先加入全局並發隊列"); }); dispatch_async(globalQueue, ^{ NSLog(@"次加入全局並發隊列"); }); NSLog(@"next task");控制台輸出如下:
2015-11-18 16:54:52.202 Whisper[39827:403208] current task 2015-11-18 16:54:52.203 Whisper[39827:403208] next task 2015-11-18 16:54:52.205 Whisper[39827:403309] 最先加入全局並發隊列 2015-11-18 16:54:52.205 Whisper[39827:403291] 次加入全局並發隊列異步線程的執行順序是不確定的。幾乎同步開始執行
自定義串行隊列
獲取自定義串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"%s",dispatch_queue_get_label(conCurrentQueue)) ;
控制台輸出:
2015-11-19 11:05:34.469 Whisper[1223:42960] com.dullgrass.serialQueue
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)函數中第一個參數是給這個queue起的標識,這個在調試的可以看到是哪個隊列在執行,或者在crash日志中,也能做為提示。第二個是需要創建的隊列類型,是串行的還是並發的
自定義串行隊列同步執行任務
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"current task"); dispatch_sync(serialQueue, ^{ NSLog(@"最先加入自定義串行隊列"); sleep(2); }); dispatch_sync(serialQueue, ^{ NSLog(@"次加入自定義串行隊列"); }); NSLog(@"next task");
控制台輸出:
2015-11-18 17:09:40.025 Whisper[40241:416296] current task 2015-11-18 17:09:40.027 Whisper[40241:416296] 最先加入自定義串行隊列 2015-11-18 17:09:43.027 Whisper[40241:416296] 次加入自定義串行隊列 2015-11-18 17:09:43.027 Whisper[40241:416296] next task
當前線程等待串行隊列中的子線程執行完成之後再執行,串行隊列中先進來的子線程先執行任務,執行完成後,再執行隊列中後面的任務。
自定義串行隊列嵌套執行同步任務,產生死鎖
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(serialQueue, ^{ //該代碼段後面的代碼都不會執行,程序被鎖定在這裡 NSLog(@"會執行的代碼"); dispatch_sync(serialQueue, ^{ NSLog(@"代碼不執行"); }); });
異步執行串行隊列,嵌套同步執行串行隊列,同步執行的串行隊列中的任務將不會被執行,其他程序正常執行
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ NSLog(@"會執行的代碼"); dispatch_sync(serialQueue, ^{ NSLog(@"代碼不執行"); }); });
注意不要嵌套使用同步執行的串行隊列任務
自定義並發隊列
獲取自定義並發隊列dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);自定義並發隊列執行同步任務
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"current task"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"先加入隊列"); }); dispatch_sync(conCurrentQueue, ^{ NSLog(@"次加入隊列"); }); NSLog(@"next task");控制台輸出如下:
2015-11-19 10:36:23.259 Whisper[827:20596] current task 2015-11-19 10:36:23.261 Whisper[827:20596] 先加入隊列 2015-11-19 10:36:23.261 Whisper[827:20596] 次加入隊列 2015-11-19 10:36:23.261 Whisper[827:20596] next task自定義並發隊列嵌套執行同步任務(不會產生死鎖,程序正常運行)
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"current task"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"先加入隊列"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"次加入隊列"); }); }); NSLog(@"next task");控制台輸出如下:
2015-11-19 10:39:21.301 Whisper[898:22273] current task 2015-11-19 10:39:21.303 Whisper[898:22273] 先加入隊列 2015-11-19 10:39:21.303 Whisper[898:22273] 次加入隊列 2015-11-19 10:39:21.303 Whisper[898:22273] next task自定義並發隊列執行異步任務
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"current task"); dispatch_async(conCurrentQueue, ^{ NSLog(@"先加入隊列"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"次加入隊列"); }); NSLog(@"next task");控制台輸出如下:
2015-11-19 10:45:22.290 Whisper[1050:26445] current task 2015-11-19 10:45:22.290 Whisper[1050:26445] next task 2015-11-19 10:45:22.290 Whisper[1050:26505] 次加入隊列 2015-11-19 10:45:22.290 Whisper[1050:26500] 先加入隊列
異步執行任務,開啟新的子線程,不影響當前線程任務的執行,並發隊列中的任務,幾乎是同步執行的,輸出順序不確定
當遇到需要執行多個線程並發執行,然後等多個線程都結束之後,再匯總執行結果時可以用group queue
使用場景: 同時下載多個圖片,所有圖片下載完成之後去更新UI(需要回到主線程)或者去處理其他任務(可以是其他線程隊列)。原理:使用函數dispatch_group_create創建dispatch group,然後使用函數dispatch_group_async來將要執行的block任務提交到一個dispatch queue。同時將他們添加到一個組,等要執行的block任務全部執行完成之後,使用dispatch_group_notify函數接收完成時的消息。使用示例:
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_group_t groupQueue = dispatch_group_create(); NSLog(@"current task"); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ NSLog(@"並行任務1"); }); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ NSLog(@"並行任務2"); }); dispatch_group_notify(groupQueue, mainQueue, ^{ NSLog(@"groupQueue中的任務 都執行完成,回到主線程更新UI"); }); NSLog(@"next task");
控制台輸出:
2015-11-19 13:47:55.117 Whisper[1645:97116] current task 2015-11-19 13:47:55.117 Whisper[1645:97116] next task 2015-11-19 13:47:55.119 Whisper[1645:97178] 並行任務1 2015-11-19 13:47:55.119 Whisper[1645:97227] 並行任務2 2015-11-19 13:47:55.171 Whisper[1645:97116] groupQueue中的任務 都執行完成,回到主線程更新UI
在當前線程阻塞的同步等待dispatch_group_wait
dispatch_group_t groupQueue = dispatch_group_create(); dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC); dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ long isExecuteOver = dispatch_group_wait(groupQueue, delayTime); if (isExecuteOver) { NSLog(@"wait over"); } else { NSLog(@"not over"); } NSLog(@"並行任務1"); }); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ NSLog(@"並行任務2"); });
控制台輸出如下:
2015-11-19 14:37:29.514 Whisper[2426:126683] current task 2015-11-19 14:37:29.518 Whisper[2426:126791] 並行任務2 2015-11-19 14:37:39.515 Whisper[2426:126733] wait over 2015-11-19 14:37:39.516 Whisper[2426:126733] 並行任務1
dispatch_time(dispatch_time_t when, int64_t delta);
參數注釋:
第一個參數一般是DISPATCH_TIME_NOW,表示從現在開始
第二個參數是延時的具體時間
延時1秒可以寫成如下幾種:
NSEC_PER_SEC----每秒有多少納秒
dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在納秒的基礎上)
dispatch_time(DISPATCH_TIME_NOW, 1000*USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少納秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---納秒
dispatch_after延時添加到隊列
使用示例:dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC); dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC); dispatch_queue_t mainQueue = dispatch_get_main_queue(); NSLog(@"current task"); dispatch_after(delayTime3, mainQueue, ^{ NSLog(@"3秒之後添加到隊列"); }); dispatch_after(delayTime2, mainQueue, ^{ NSLog(@"2秒之後添加到隊列"); }); NSLog(@"next task");控制台輸出如下:
2015-11-19 15:50:19.369 Whisper[2725:172593] current task 2015-11-19 15:50:19.370 Whisper[2725:172593] next task 2015-11-19 15:50:21.369 Whisper[2725:172593] 2秒之後添加到隊列 2015-11-19 15:50:22.654 Whisper[2725:172593] 3秒之後添加到隊列
dispatch_after只是延時提交block,並不是延時後立即執行,並不能做到精確控制,需要精確控制的朋友慎用哦
dispatch_apply在給定的隊列上多次執行某一任務,在主線程直接調用會阻塞主線程去執行block中的任務。
dispatch_apply函數的功能:把一項任務提交到隊列中多次執行,隊列可以是串行也可以是並行,dispatch_apply不會立刻返回,在執行完block中的任務後才會返回,是同步執行的函數。dispatch_apply正確使用方法:為了不阻塞主線程,一般把dispatch_apply放在異步隊列中調用,然後執行完成後通知主線程使用示例:dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0); //第一個參數,3--block執行的次數 //第二個參數,applyQueue--block任務提交到的隊列 //第三個參數,block--需要重復執行的任務 dispatch_apply(3, applyQueue, ^(size_t index) { NSLog(@"current index %@",@(index)); sleep(1); }); NSLog(@"dispatch_apply 執行完成"); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ NSLog(@"回到主線程更新UI"); }); }); NSLog(@"next task");控制台輸出如下:
2015-11-19 16:24:45.015 Whisper[4034:202269] current task 2015-11-19 16:24:45.016 Whisper[4034:202269] next task 2015-11-19 16:24:45.016 Whisper[4034:202347] current index 0 2015-11-19 16:24:45.016 Whisper[4034:202344] current index 1 2015-11-19 16:24:45.016 Whisper[4034:202345] current index 2 2015-11-19 16:24:46.021 Whisper[4034:202347] dispatch_apply 執行完成 2015-11-19 16:24:46.021 Whisper[4034:202269] 回到主線程更新UI嵌套使用dispatch_apply會導致死鎖。
dispatch_once保證在app運行期間,block中的代碼只執行一次
經典使用場景---單例單例對象ShareManager的定義:ShareManager的.h文件 #importdispatch_barrier_async 柵欄的作用 功能:是在並行隊列中,等待在dispatch_barrier_async之前加入的隊列全部執行完成之後(這些任務是並發執行的)再執行dispatch_barrier_async中的任務,dispatch_barrier_async中的任務執行完成之後,再去執行在dispatch_barrier_async之後加入到隊列中的任務(這些任務是並發執行的)。使用示例:@interface ShareManager : NSObject @property (nonatomic, copy) NSString *someProperty; + (ShareManager *)shareManager; + (ShareManager *)sharedManager; @end ShareManager的.m文件 #import "ShareManager.h" @implementation ShareManager static ShareManager *sharedManager = nil; //GCD實現單例功能 + (ShareManager *)shareManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[self alloc] init]; }); return sharedManager; } //在ARC下,非GCD,實現單例功能 + (ShareManager *)sharedManager { @synchronized(self) { if (!sharedManager) { sharedManager = [[self alloc] init]; } } return sharedManager; } - (instancetype)init{ self = [super init]; if (self) { _someProperty =@"Default Property Value"; } return self; } @end ShareManager的使用 #import "ShareManager.h" 在需要使用的函數中,直接調用下面的方法 ShareManager *share = [ShareManager sharedManager]; NSLog(@"share is %@",share.someProperty);
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(conCurrentQueue, ^{ NSLog(@"dispatch 1"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"dispatch 2"); }); dispatch_barrier_async(conCurrentQueue, ^{ NSLog(@"dispatch barrier"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"dispatch 3"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"dispatch 4"); });控制台輸出如下:
2015-11-19 18:12:34.125 Whisper[22633:297257] dispatch 1 2015-11-19 18:12:34.125 Whisper[22633:297258] dispatch 2 2015-11-19 18:12:34.126 Whisper[22633:297258] dispatch barrier 2015-11-19 18:12:34.127 Whisper[22633:297258] dispatch 3 2015-11-19 18:12:34.127 Whisper[22633:297257] dispatch 4