一. GCD的介紹
GCD屬於系統級的執行緒管理,在Dispatch queue中執行需要執行的任務效能非常的高。GCD這塊已經開源,地址:http://libdispatch.macosforge.org。GCD中的FIFO佇列稱為dispatch queue,用來保證先進來的任務先得到執行。這裡我們從作用和場景同時分析,看一下到底在哪些地方會使用。單單隻介紹作用的話,可能
二. 常用的GCD
1. dispatch_semaphore_t,dispatch_semaphore_wait,dispatch_semaphore_signal
作用:
在多執行緒下控制多執行緒的併發數目。
- 建立訊號量,可以設定訊號量的資源數。0表示沒有資源,呼叫
dispatch_semaphore_wait
會立即等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)
; - 等待訊號,可以設定超時引數。該函式返回0表示得到通知,非0表示超時。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
; - 通知訊號,如果等待執行緒被喚醒則返回非0,否則返回0。
dispatch_semaphore_signal(semaphore);
使用場景一:併發佇列
比如現在我每次想執行10個任務。休息兩秒。繼續執行10個任務。可以這麼寫.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
-(void) testSemaphore{ dispatch_group_t group = dispatch_group_create(); dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); //訊號總量是10 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i = 0; i 100; i++) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//訊號量-1 dispatch_group_async(group, queue, ^{ NSLog(@"%i",i); sleep(2); dispatch_semaphore_signal(semaphore); //訊號量+1 }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } ``` 執行結果: ```objc 2016-06-01 20:30:35.113 XXGCD[39559:306278] 7 2016-06-01 20:30:35.113 XXGCD[39559:306277] 6 2016-06-01 20:30:35.113 XXGCD[39559:306271] 0 2016-06-01 20:30:35.113 XXGCD[39559:306274] 3 2016-06-01 20:30:35.113 XXGCD[39559:306270] 1 2016-06-01 20:30:35.113 XXGCD[39559:306272] 2 2016-06-01 20:30:35.113 XXGCD[39559:306275] 4 2016-06-01 20:30:35.113 XXGCD[39559:306276] 5 2016-06-01 20:30:35.113 XXGCD[39559:306279] 8 2016-06-01 20:30:35.113 XXGCD[39559:306280] 9 2016-06-01 20:30:37.117 XXGCD[39559:306272] 11 2016-06-01 20:30:37.117 XXGCD[39559:306270] 12 2016-06-01 20:30:37.117 XXGCD[39559:306275] 10 2016-06-01 20:30:37.117 XXGCD[39559:306280] 13 2016-06-01 20:30:37.117 XXGCD[39559:306276] 15 2016-06-01 20:30:37.117 XXGCD[39559:306279] 14 2016-06-01 20:30:37.117 XXGCD[39559:306274] 16 2016-06-01 20:30:37.117 XXGCD[39559:306277] 17 2016-06-01 20:30:37.117 XXGCD[39559:306278] 18 2016-06-01 20:30:37.117 XXGCD[39559:306271] 19 ... |
使用場景二:非同步佇列中做事,等待回撥後執行某件事
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-(void) testAsynFinished{ __block BOOL isok = NO; dispatch_semaphore_t sema = dispatch_semaphore_create(0); XXEngine *engine = [[XXEngine alloc] init]; [engine queryCompletion:^(BOOL isOpen) {//sleep了3秒 isok = isOpen; dispatch_semaphore_signal(sema); NSLog(@"success!"); } onError:^(BOOL isOpen, int errorCode) { isok = NO; dispatch_semaphore_signal(sema); NSLog(@"error!"); }]; NSLog(@"finished"); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); } |
執行結果:
1 2 |
2016-06-02 15:01:06.168 XXGCD[35443:290673] success! 2016-06-02 15:01:06.168 XXGCD[35443:290673] finished |
使用場景三:生產者,消費者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
-(void) testProductAndConsumer{ dispatch_semaphore_t sem = dispatch_semaphore_create(0); dispatch_queue_t producerQueue = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);//生產者執行緒跑的佇列 dispatch_queue_t consumerQueue = dispatch_queue_create("consumer", DISPATCH_QUEUE_CONCURRENT);//消費者執行緒跑的佇列 __block int cakeNumber = 0; dispatch_async(producerQueue, ^{ //生產者佇列 while (1) { if (!dispatch_semaphore_signal(sem)) { NSLog(@"Product:生產出了第%d個蛋糕",++cakeNumber); sleep(1); //wait for a while continue; } } }); dispatch_async(consumerQueue, ^{//消費者佇列 while (1) { if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 0*NSEC_PER_SEC))){ if(cakeNumber > 0){ NSLog(@"Consumer:拿到了第%d個蛋糕",cakeNumber--); } continue; } } }); } |
執行結果:
1 2 3 4 |
2016-06-02 15:04:18.785 XXGCD[35593:294211] Product:生產出了第1個蛋糕 2016-06-02 15:04:18.785 XXGCD[35593:294209] Consumer:拿到了第1個蛋糕 2016-06-02 15:04:19.790 XXGCD[35593:294209] Consumer:拿到了第1個蛋糕 2016-06-02 15:04:19.790 XXGCD[35593:294211] Product:生產出了第1個蛋糕 |
2. dispatch_group_t,dispatch_group_notify
用來阻塞一個執行緒,直到一個或多個任務完成執行。有時候你必須等待任務完成的結果,然後才能繼續後面的處理
使用場景一:併發佇列
1 2 3 4 5 6 7 8 9 10 11 12 |
-(void) testGCDGroup{ dispatch_group_t group = dispatch_group_create(); for(int i = 0; i 10; i++) { dispatch_group_enter(group); [[XXEngine instance] doAsyncWorkWithCompletionBlock:^{ dispatch_group_leave(group); }]; } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"testGCDGroup1 更新UI操作"); } |
等價於:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
-(void) testGCDGroup2{ dispatch_group_t group = dispatch_group_create(); for(int i = 0; i 10; i++) { dispatch_group_enter(group); [[XXEngine instance] doAsyncWorkWithCompletionBlock:^{ dispatch_group_leave(group); }]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"testGCDGroup2 更新UI操作"); }); } ``` 執行結果: ```objc 2016-06-02 15:29:54.392 XXGCD[36197:313551] test 2016-06-02 15:29:54.392 XXGCD[36197:313569] test 2016-06-02 15:29:54.393 XXGCD[36197:313572] test 2016-06-02 15:29:54.393 XXGCD[36197:313571] test 2016-06-02 15:29:54.392 XXGCD[36197:313558] test 2016-06-02 15:29:54.392 XXGCD[36197:313568] test 2016-06-02 15:29:54.392 XXGCD[36197:313563] test 2016-06-02 15:29:54.393 XXGCD[36197:313570] test 2016-06-02 15:29:54.393 XXGCD[36197:313573] test 2016-06-02 15:29:54.393 XXGCD[36197:313574] test 2016-06-02 15:29:56.398 XXGCD[36197:313513] testGCDGroup1 更新UI操作 |
使用場景二:多工執行完成,重新整理UI
需要在主執行緒中執行,比如操作GUI,那麼我們只要將main queue而非全域性佇列傳給dispatch_group_notify函式就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-(void) testGCDGroup3{ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"group1"); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"group2"); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"group3"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"testGCDGroup3 更新UI操作"); }); } |
執行結果:
1 2 3 4 |
2016-06-02 15:58:29.041 XXGCD[36675:337871] group1 2016-06-02 15:58:30.041 XXGCD[36675:337880] group2 2016-06-02 15:58:31.043 XXGCD[36675:337884] group3 2016-06-02 15:58:31.043 XXGCD[36675:337834] testGCDGroup3 更新UI操作 |
3. dispatch_apply
是同步函式,會阻塞當前執行緒直到所有迴圈迭代執行完成。當提交到併發queue時,迴圈迭代的執行順序是不確定的
1 2 3 4 5 6 7 8 9 10 11 |
static int i = 0; -(void) testGCDApply{ dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, globalQ, ^(size_t index) { // 執行10次 [self printTest]; }); } -(void) printTest{ NSLog(@"%d",++i); } |
執行結果:
1 2 3 4 5 6 7 8 9 10 |
2016-06-02 16:06:50.799 XXGCD[37133:347348] 6 2016-06-02 16:06:50.799 XXGCD[37133:347329] 2 2016-06-02 16:06:50.799 XXGCD[37133:347346] 8 2016-06-02 16:06:50.799 XXGCD[37133:347336] 1 2016-06-02 16:06:50.799 XXGCD[37133:347340] 3 2016-06-02 16:06:50.799 XXGCD[37133:347296] 4 2016-06-02 16:06:50.799 XXGCD[37133:347347] 7 2016-06-02 16:06:50.799 XXGCD[37133:347349] 5 2016-06-02 16:06:50.799 XXGCD[37133:347329] 9 2016-06-02 16:06:50.799 XXGCD[37133:347348] 10 |
4. dispatch_source_set_timer,dispatch_suspend,dispatch_resume,dispatch_source_cancel
1 2 3 |
`dispatch_suspend`:暫停`dispatch_source_t` `dispatch_resume`:恢復`dispatch_source_t` `dispatch_cancel`:取消`dispatch_source_t` |
眾所周知,定時器有NSTimer,但是NSTimer有如下弊端:
- 必須保證有一個活躍的runloop,子執行緒的runloop是預設關閉的。這時如果不手動啟用runloop,performSelector和scheduledTimerWithTimeInterval的呼叫將是無效的
- NSTimer的建立與撤銷必須在同一個執行緒操作、performSelector的建立與撤銷必須在同一個執行緒操作。
- 記憶體管理有潛在洩露的風險
1 |
_timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doSometing) userInfo:nil repeats:YES]; |
如果此時不呼叫
1 |
[_timer invalidate]; |
是無法停止的。還有持有self,造成物件無法釋放。
所以在此我還是比較推薦用dispatch的timer,無須考慮這些事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
@interface XXGCDTimer() { dispatch_source_t _timer; } @end @implementation XXGCDTimer -(void) startGCDTimer{ NSTimeInterval period = 1.0; //設定時間間隔 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒執行 dispatch_source_set_event_handler(_timer, ^{ //在這裡執行事件 NSLog(@"每秒執行test"); }); dispatch_resume(_timer); } -(void) pauseTimer{ if(_timer){ dispatch_suspend(_timer); } } -(void) resumeTimer{ if(_timer){ dispatch_resume(_timer); } } -(void) stopTimer{ if(_timer){ dispatch_source_cancel(_timer); _timer = nil; } } @end |
5. dispatch_barrier_async
這裡之前的文章有非常詳細的解釋以及demo在文章後面。可以參考 iOS非同步讀寫總結之NSDictionary,這裡就不去詳細說明了。 注意一下DISPATCH_QUEUE_CONCURRENT
(並行佇列)和DISPATCH_QUEUE_SERIAL
(序列佇列)即可
6. dispatch_sync
和dispatch_async
這裡一個是同步,一個是非同步。只要注意一下死鎖的問題就好了。
錯誤場景1: (死鎖)結果無法輸出(呼叫場景限於主執行緒呼叫主執行緒)
1 2 3 4 5 |
-(void) startSYNC{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"3"); }); } |
原因:
1:dispatch_sync
在等待block語句執行完成,而block語句需要在主執行緒裡執行,所以dispatch_sync
如果在主執行緒呼叫就會造成死鎖
2:dispatch_sync
是同步的,本身就會阻塞當前執行緒,也即主執行緒。而又往主執行緒裡塞進去一個block,所以就會發生死鎖。
錯誤場景2: (死鎖)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- (void)testSYNC2 { dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("I.am.an.iOS.developer", DISPATCH_QUEUE_SERIAL); dispatch_sync(aSerialDispatchQueue, ^{ // block 1 NSLog(@"Good Night, Benjamin."); dispatch_sync(aSerialDispatchQueue, ^{ // block 2 NSLog(@"Good Night, Daisy."); }); }); } ``` 原因: 簡單來說,在dispatch_sync巢狀使用時要注意:不能在一個巢狀中使用同一個serial dispatch queue,因為會發生死鎖; ##### 正確用法: ```objc dispatch_async(dispatch_get_main_queue(), ^{ //async 非同步佇列,dispatch_async 函式會立即返回, block會在後臺非同步執行。 NSLog(@"2");//不會造成死鎖; }); |
7. dispatch_get_global_queue
1 2 3 4 5 6 7 8 9 |
-(void) test{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 耗時的操作 dispatch_async(dispatch_get_main_queue(), ^{ // 更新介面 NSLog(@"update UI"); }); }); } |
全域性併發佇列的優先順序
1 2 3 4 |
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 預設(中) #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 後臺 |
注意:
在實際修改bug的時候發現,global全域性佇列並不會反覆建立新的執行緒,在系統啟動後底層會維護一個執行緒池的概念,一旦用Global的佇列結束,會快取一段時間。因為在測試的時候可以列印出Thread ID。在多執行緒情況下發現一些ThreadID是一樣的,由此得出結論。
8. dispatch_once
1 2 3 4 5 |
// 使用dispatch_once函式能保證某段程式碼在程式執行過程中只被執行1次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 只執行1次的程式碼(這裡面預設是執行緒安全的) }); |
9. dispatch_after
1 2 3 |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2秒後非同步執行這裡的程式碼... }); |
三. 總結
這裡是對GCD做總結的同時,突出了一些使用場景。因為在開發過程中,我們不僅僅知道這東西是啥,更要知道什麼時候靈活運用,這裡提供了一些解決思路。關於Block相關的方法可以參考Block那些事.最後歡迎加入QQ群:237305299。一起探討iOS技術問題。