菜鳥不要怕,看一眼,你就會用GCD,帶你裝逼帶你飛

原文鏈接:蘋果核

相信讀者已經看過很多大神們對GCD深入淺出的分析,這也是老生常談的一個多線程的實現方式了,所以我也就不再啰嗦其理論。但是到底有多少方法是我們日常編程中常用的?又有多少是你不知道的?今天,我就來例舉一些GCD的方法,絕對讓你看一眼就會正確得使用。

與其說CGD是線程管理,不如說是隊列管理,那么我們先來介紹一下GCD中常用的隊列:

Serial Diapatch Queue 串行隊列

當任務相互依賴,具有明顯的先后順序的時候,使用串行隊列是一個不錯的選擇
創建一個串行隊列:

dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL); 

第一個參數為隊列名,第二個參數為隊列類型,當然,第二個參數人如果寫NULL,創建出來的也是一個串行隊列。然后我們在異步線程來執行這個隊列:

dispatch_async(serialDiapatchQueue, ^{  
    NSLog(@"1");  
});  
    
dispatch_async(serialDiapatchQueue, ^{  
    sleep(2);  
    NSLog(@"2");  
});  
    
dispatch_async(serialDiapatchQueue, ^{  
    sleep(1);  
    NSLog(@"3");  
});  

為了能更好的理解,我給每個異步線程都添加了一個log,看一下日志平臺的log:

2016-03-07 10:17:13.907 GCD[2195:61497] 1
2016-03-07 10:17:15.911 GCD[2195:61497] 2
2016-03-07 10:17:16.912 GCD[2195:61497] 3

沒錯,他在61497這個編號的線程中做了串行輸出,相互彼此依賴,串行執行

Concurrent Diapatch Queue 并發隊列

與串行隊列剛好相反,他不會存在任務間的相互依賴。

創建一個并發隊列:

dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

比較2個隊列的創建,我們發現只有第二個參數從DISPATCH_QUEUE_SERIAL變成了對應的DISPATCH_QUEUE_CONCURRENT,其他完全一樣。

用同一段代碼,換一種隊列我們來比較一下效果:

dispatch_async(concurrentDiapatchQueue, ^{
    NSLog(@"1");
});
dispatch_async(concurrentDiapatchQueue, ^{
    sleep(2);
    NSLog(@"2");
});
dispatch_async(concurrentDiapatchQueue, ^{
    sleep(1);
    NSLog(@"3");
});

輸出的log:

2016-03-07 10:42:38.289 GCD[2260:72557] 1
2016-03-07 10:42:39.291 GCD[2260:72559] 3
2016-03-07 10:42:40.293 GCD[2260:72556] 2

我們發現,log的輸出在3個不同編號的線程中進行,而且相互不依賴,不阻塞。

Global Queue & Main Queue

這是系統為我們準備的2個隊列:

  • Global Queue其實就是系統創建的Concurrent Diapatch Queue
  • Main Queue 其實就是系統創建的位于主線程的Serial Diapatch Queue

通常情況我們會把這2個隊列放在一起使用,也是我們最常用的開異步線程-執行異步任務-回主線程的一種方式:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"異步線程");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"異步主線程");
    });
});

通過上面的代碼我們發現了2個有意思的點:

  • dispatch_get_global_queue存在優先級,沒錯,他一共有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
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    NSLog(@"4");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"1");
});

在指定優先級之后,同一個隊列會按照這個優先級執行,打印的順序為1、2、3、4,當然這不是串行隊列,所以不存在絕對回調先后。

  • 異步主線程

    • 在日常工作中,除了在其他線程返回主線程的時候需要用這個方法,還有一些時候我們在主線程中直接調用異步主線程,這是利用dispatch_async的特性:block中的任務會放在主線程本次runloop之后返回。這樣,有些存在先后順序的問題就可以得到解決了。

說完了隊列,我們再說說GCD提供的一些操作隊列的方法

dispatch_set_target_queue

剛剛我們說了系統的Global Queue是可以指定優先級的,那我們如何給自己創建的隊列執行優先級呢?這里我們就可以用到dispatch_set_target_queue這個方法:

dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(serialDiapatchQueue, dispatchgetglobalqueue);
dispatch_async(serialDiapatchQueue, ^{
    NSLog(@"我優先級低,先讓讓");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"我優先級高,我先block");
});

我把自己創建的隊列塞到了系統提供的global_queue隊列中,我們可以理解為:我們自己創建的queue其實是位于global_queue中執行,所以改變global_queue的優先級,也就改變了我們自己所創建的queue的優先級。所以我們常用這種方式來管理子隊列。

dispatch_after

這個是最常用的,用來延遲執行的GCD方法,因為在主線程中我們不能用sleep來延遲方法的調用,所以用它是最合適的,我們做一個簡單的例子:

NSLog(@"小破孩-波波1");
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"小破孩-波波2");
});

輸出的結果:

2016-03-07 11:25:06.019 GCD[2443:95722] 小破孩-波波1
2016-03-07 11:25:08.019 GCD[2443:95722] 小破孩-波波2

我們看到他就是在主線程,就是剛好延遲了2秒,當然,我說這個2秒并不是絕對的,為什么這么說?還記得我之前在介紹dispatch_async這個特性的時候提到的嗎?他的block中方法的執行會放在主線程runloop之后,所以,如果此時runloop周期較長的時候,可能會有一些時差產生。

dispatch_group

當我們需要監聽一個并發隊列中,所有任務都完成了,就可以用到這個group,因為并發隊列你并不知道哪一個是最后執行的,所以以單獨一個任務是無法監聽到這個點的,如果把這些單任務都放到同一個group,那么,我們就能通過dispatch_group_notify方法知道什么時候這些任務全部執行完成了。

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, ^{NSLog(@"0");});
dispatch_group_async(group, queue, ^{NSLog(@"1");});
dispatch_group_async(group, queue, ^{NSLog(@"2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"down");});

在例子中,我把3個log分別放在并發隊列中,通過把這個并發隊列任務統一加入group中,group每次runloop的時候都會調用一個方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用來檢查group中的任務是否已經完成,如果已經完成了,那么會執行dispatch_group_notify的block,輸出'down'看一下運行結果:

2016-03-07 14:21:58.647 GCD[9424:156388] 2
2016-03-07 14:21:58.647 GCD[9424:156382] 0
2016-03-07 14:21:58.647 GCD[9424:156385] 1
2016-03-07 14:21:58.650 GCD[9424:156324] down

dispatch_barrier_async

此方法的作用是在并發隊列中,完成在它之前提交到隊列中的任務后打斷,單獨執行其block,并在執行完成之后才能繼續執行在他之后提交到隊列中的任務:

dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"0");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"1");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"2");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"3");});
dispatch_barrier_async(concurrentDiapatchQueue, ^{sleep(1); NSLog(@"4");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"5");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"6");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"7");});
dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"8");});

輸出的結果為:

2016-03-07 14:45:32.410 GCD[10079:169655] 1
2016-03-07 14:45:32.410 GCD[10079:169658] 2
2016-03-07 14:45:32.410 GCD[10079:169656] 0
2016-03-07 14:45:32.410 GCD[10079:169661] 3
2016-03-07 14:45:33.414 GCD[10079:169661] 4
2016-03-07 14:45:33.415 GCD[10079:169661] 5
2016-03-07 14:45:33.415 GCD[10079:169658] 6
2016-03-07 14:45:33.415 GCD[10079:169655] 8
2016-03-07 14:45:33.415 GCD[10079:169662] 7

4之后的任務在我線程sleep之后才執行,這其實就起到了一個線程鎖的作用,在多個線程同時操作一個對象的時候,讀可以放在并發進行,當寫的時候,我們就可以用dispatch_barrier_async方法,效果杠杠的。

dispatch_sync

  • dispatch_sync 會在當前線程執行隊列,并且阻塞當前線程中之后運行的代碼,所以,同步線程非常有可能導致死鎖現象,我們這邊就舉一個死鎖的例子,直接在主線程調用以下代碼:
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"有沒有同步主線程?");
});

根據FIFO(先進先出)的原則,block里面的代碼應該在主線程此次runloop后執行,但是猶豫他是同步隊列,所有他之后的代碼會等待其執行完成后才能繼續執行,2者相互等待,所以就出現了死鎖。

我們再舉一個比較特殊的例子:

dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{sleep(1);NSLog(@"1");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"2");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"3");});
NSLog(@"4");

其打印結果為:

2016-03-07 17:15:48.124 GCD[14198:272683] 1
2016-03-07 17:15:49.125 GCD[14198:272683] 2
2016-03-07 17:15:50.126 GCD[14198:272683] 3
2016-03-07 17:15:50.126 GCD[14198:272683] 4

從線程編號中我們發現,同步方法沒有去開新的線程,而是在當前線程中執行隊列,會有人問,上文說dispatch_get_global_queue不是并發隊列,并發隊列不是應該會在開啟多個線程嗎?這個前提是用異步方法。GCD其實是弱化了線程的管理,強化了隊列管理,這使我們理解變得比較形象。

dispatch_apply

這個方法用于無序查找,在一個數組中,我們能開啟多個線程來查找所需要的值,我這邊也舉個例子:

NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
    NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
NSLog(@"阻塞");

輸出結果:

2016-03-07 17:36:50.726 GCD[14318:291701] 1=1
2016-03-07 17:36:50.726 GCD[14318:291705] 0=0
2016-03-07 17:36:50.726 GCD[14318:291783] 3=3
2016-03-07 17:36:50.726 GCD[14318:291782] 2=2
2016-03-07 17:36:50.726 GCD[14318:291784] 5=5
2016-03-07 17:36:50.726 GCD[14318:291627] 4=4
2016-03-07 17:36:50.726 GCD[14318:291785] 6=6
2016-03-07 17:36:50.727 GCD[14318:291627] 阻塞

通過輸出log,我們發現這個方法雖然會開啟多個線程來遍歷這個數組,但是在遍歷完成之前會阻塞主線程。

dispatch_suspend & dispatch_resume

隊列掛起和恢復,這個沒什么好說的,直接上代碼:

dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDiapatchQueue, ^{
    for (int i=0; i<100; i++)
    {
        NSLog(@"%i",i);
        if (i==50)
        {
            NSLog(@"-----------------------------------");
            dispatch_suspend(concurrentDiapatchQueue);
            sleep(3);
            dispatch_async(dispatch_get_main_queue(), ^{
                dispatch_resume(concurrentDiapatchQueue);
            });
        }
    }
});

我們甚至可以在不同的線程對這個隊列進行掛起和恢復,因為GCD是對隊列的管理。

Semaphore

我們可以通過設置信號量的大小,來解決并發過多導致資源吃緊的情況,以單核CPU做并發為例,一個CPU永遠只能干一件事情,那如何同時處理多個事件呢,聰明的內核工程師讓CPU干第一件事情,一定時間后停下來,存取進度,干第二件事情以此類推,所以如果開啟非常多的線程,單核CPU會變得非常吃力,即使多核CPU,核心數也是有限的,所以合理分配線程,變得至關重要,那么如何發揮多核CPU的性能呢?如果讓一個核心模擬傳很多線程,經常干一半放下干另一件事情,那效率也會變低,所以我們要合理安排,將單一任務或者一組相關任務并發至全局隊列中運算或者將多個不相關的任務或者關聯不緊密的任務并發至用戶隊列中運算,所以用好信號量,合理分配CPU資源,程序也能得到優化,當日常使用中,信號量也許我們只起到了一個計數的作用,真的有點大材小用。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//為了讓一次輸出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次,信號量-1;進來10次后就一直hold住,直到信號量大于0;
    dispatch_async(queue, ^{
        NSLog(@"%i",i);
        sleep(2);
        dispatch_semaphore_signal(semaphore);//由于這里只是log,所以處理速度非???,我就模擬2秒后信號量+1;
    });
} 

dispatch_once

這個函數一般是用來做一個真的單例,也是非常常用的,在這里就舉一個單例的例子吧:

static SingletonTimer * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    instance = [[SingletonTimer alloc] init];
});

return instance;

好了,blog說了這么多關于GCD中的方法,大家是不是覺得這篇blog并沒有什么高深的理論,本文更傾向于實用,看完這篇blog之后,大家一定對GCD躍躍欲試了吧!

參考文獻:《Objective-C高級編程 iOS與OS X多線程》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容