學(xué)習(xí)GCD看我就夠了

什么是多線(xiàn)程?
計(jì)算機(jī)在運(yùn)行一段程序的時(shí)候,會(huì)把該程序的CPU命令列配置到內(nèi)存中,然后按照順序一個(gè)一個(gè)執(zhí)行命令列,這樣1個(gè)CPU執(zhí)行的CPU命令列為一條無(wú)分叉路徑就是線(xiàn)程。
而有多條這樣的執(zhí)行指令列的路徑存在時(shí)即為多線(xiàn)程。
iOS實(shí)現(xiàn)多線(xiàn)程有4種方法

  • pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueuef

這里我們主要講GCD

一、Dispatch Queue和線(xiàn)程的關(guān)系

什么是Dispatch Queue?
如其名稱(chēng),是執(zhí)行處理的等待隊(duì)列。當(dāng)我們通過(guò)dispatch_async等函數(shù)把Block加入Dispatch Queue后,Dispatch Queue按照追加的順序(FIFO)執(zhí)行處理。

通過(guò)Dispatch Queue執(zhí)行處理

Dispatch Queue的種類(lèi)

  • Serial Dispatch Queue(串行隊(duì)列) ——等待現(xiàn)在執(zhí)行中處理結(jié)束再加入隊(duì)列
  • Concurrent Dispatch Queue(并發(fā)隊(duì)列) ——不等待現(xiàn)在執(zhí)行中處理結(jié)束,直接加入隊(duì)列
Serial Dispatch Queue
Concurrent Dispatch Queue

用代碼說(shuō)明:

Serial Dispatch Queue

    dispatch_queue_t serial_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial_queue, ^{
        NSLog(@"block 1");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 2");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 3");
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"block 4");
    });

輸出如下:

2017-09-27 11:43:40.230126+0800 aegewgr[4327:1296458] block 1
2017-09-27 11:43:40.230335+0800 aegewgr[4327:1296458] block 2
2017-09-27 11:43:40.230461+0800 aegewgr[4327:1296458] block 3
2017-09-27 11:43:40.230548+0800 aegewgr[4327:1296458] block 4

這里Serial Dispatch Queue只會(huì)使用一個(gè)線(xiàn)程,因?yàn)樗谴嘘?duì)列,只會(huì)當(dāng)一個(gè)處理執(zhí)行完了才會(huì)將下一個(gè)任務(wù)交給線(xiàn)程處理。

Concurrent Dispatch Queue

    dispatch_queue_t concurrent_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 1");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 2");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 3");
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"block 4");
    });

輸出如下:

2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304484] block 3
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304483] block 1
2017-09-27 11:45:09.057522+0800 aegewgr[4349:1304486] block 4
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304485] block 2

block的執(zhí)行完成
是隨機(jī)的,因?yàn)樗麄冸m然是按順序把任務(wù)提交給線(xiàn)程,但是因?yàn)椴恍枰却耙粋€(gè)任務(wù)執(zhí)行,所以幾乎是同時(shí)交給線(xiàn)程處理的。所以這里會(huì)使用多個(gè)線(xiàn)程,而具體線(xiàn)程數(shù)的多少由XNU內(nèi)核決定。

Concurrent Dispatch Queue的執(zhí)行

二、Dispatch Queue的使用

1、獲取隊(duì)列

在使用Dispatch Queue的時(shí)候我們可以通過(guò)dispatch_queue_create函數(shù)創(chuàng)建隊(duì)列,也可以獲取系統(tǒng)給我們提供的隊(duì)列。系統(tǒng)給我們提供了兩種隊(duì)列

系統(tǒng)提供的Dispatch Queue
2、同步與異步
  • dispatch_async表示異步:將指定的Block”非同步“加入Dispatch Queue,不做任何等待
異步執(zhí)行
  • dispatch_sync表示同步:將指定的Block”同步“的加入Dispatch Queue,在Block結(jié)束之前,dispatch_sync函數(shù)會(huì)一直等待
同步執(zhí)行
3、死鎖

由于dispatch_sync會(huì)等待Block執(zhí)行結(jié)束才會(huì)繼續(xù)往下執(zhí)行,所以會(huì)產(chǎn)生死鎖的情況

我們直接在主線(xiàn)程中同步加入一個(gè)Blcok:

dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_sync(main_queue, ^{
        NSLog(@"main queue");
    });
    NSLog(@"go on");

無(wú)任何輸出,程序直接卡死了。這就是造成了死鎖
因?yàn)樵撛创a在main_queue(主線(xiàn)程)中加入一個(gè)加入一個(gè)指定的Block,并等待其執(zhí)行結(jié)束。而由于main_queue是一個(gè)串行隊(duì)列,它要等當(dāng)前線(xiàn)程中的任務(wù)處理完后才會(huì)把隊(duì)列中的任務(wù)提交到主線(xiàn)程,而主線(xiàn)程又在等待這段代碼執(zhí)行,所以造成了相互等待,就產(chǎn)生了死鎖。(而并發(fā)隊(duì)列不會(huì)產(chǎn)生死鎖)
如:

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_sync(global_queue, ^{
        NSLog(@"global_queue out");
        dispatch_sync(global_queue, ^{
            NSLog(@"global_queue in");
        });
    });

輸出如下:

2017-09-27 16:11:56.332317+0800 aegewgr[4723:1590202] global_queue out
2017-09-27 16:11:56.332446+0800 aegewgr[4723:1590202] global_queue in

所以產(chǎn)生死鎖的話(huà)一般都是在串行隊(duì)列中并且是在一個(gè)線(xiàn)程中同步往這個(gè)線(xiàn)程提交一個(gè)Block。

4、Dispatch Group(派發(fā)分組)

Dispatch Group是GCD的一項(xiàng)特性,能夠把任務(wù)分組。調(diào)用者在這組任務(wù)執(zhí)行完畢后會(huì)得到通知,并做相應(yīng)的處理。

創(chuàng)建:dispatch_group_t group = dispatch_group_create();
同樣的,它也有
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, <#^(void)block#>)dispatch_sync函數(shù)沒(méi)有什么區(qū)別,它只是多了一個(gè)dispatch_group_t參數(shù),來(lái)把任務(wù)進(jìn)行分組。
還有一種方法能把任務(wù)加入dispatch_group,那就是下面這對(duì)情侶:

    dispatch_group_enter(dispatch_group_t group);
    dispatch_group_leave(dispatch_group_t group);

記住,這對(duì)情侶一定要成對(duì)出現(xiàn),dispatch_group_enter就是標(biāo)志下面的代碼要加入dispatch_group。dispatch_group_leave就是表示加入dispatch_group的代碼結(jié)束。也就是說(shuō)dispatch_group_enter和dispatch_group_leave之間的代碼就是加入dispatch_group中的。

說(shuō)了這么多,把一個(gè)隊(duì)列加入dispatch_group后有什么用呢?主要就是一組相似的操作結(jié)束后,你可以通過(guò)dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block)函數(shù)來(lái)獲得通知,并進(jìn)行相應(yīng)的處理。Block參數(shù)就是你要添加的處理。

當(dāng)然,如果你想設(shè)置一個(gè)等待時(shí)間,可以使用dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)函數(shù),該函數(shù)設(shè)置了一個(gè)等待時(shí)間也就是說(shuō)程序要一直阻塞當(dāng)前線(xiàn)程直到group中的任務(wù)執(zhí)行完畢或者超過(guò)等待時(shí)間,才會(huì)繼續(xù)往下執(zhí)行。
dispatch_block_notify函數(shù)不會(huì)阻塞當(dāng)前線(xiàn)程,它只是指定了一個(gè)group任務(wù)執(zhí)行完后的回調(diào)。

需要舉個(gè)栗子嗎?
好吧,還是舉個(gè)栗子吧。

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_group_t group = dispatch_group_create();
    ;
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 1");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 2");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 3");
    });
    dispatch_group_notify(group, global_queue, ^{
        NSLog(@"notify");
    });
    NSLog(@"other task");

輸出如下:

2017-09-27 17:06:37.795564+0800 aegewgr[4983:1713915] other task
2017-09-27 17:06:37.795571+0800 aegewgr[4983:1714182] task 3
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714181] task 1
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714183] task 2
2017-09-27 17:06:37.795813+0800 aegewgr[4983:1714183] notify

可以看到dispatch_group_notify并沒(méi)有阻塞當(dāng)前線(xiàn)程,而且它提交的Block一定是當(dāng)group中的所有任務(wù)執(zhí)行完后才會(huì)執(zhí)行。另外,這里的queue可以不是一個(gè)queue,你可以使用任意其它queue,不過(guò)最好是并發(fā)隊(duì)列,如果是串行隊(duì)列,任務(wù)會(huì)按順序一個(gè)一個(gè)執(zhí)行,那使用group的意義就不大了。

看看dispatch_group_wait

dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    dispatch_group_t group = dispatch_group_create();
    ;
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 1");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 2");
    });
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"task 3");
    });
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1));
    NSLog(@"other task");

輸出如下:

2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726567] task 2
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726568] task 1
2017-09-27 17:10:10.968140+0800 aegewgr[5002:1726569] task 3
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726443] other task

可以看到dispatch_group_wait函數(shù)阻塞了當(dāng)前線(xiàn)程,只有當(dāng)group中的所有任務(wù)執(zhí)行完后線(xiàn)程才會(huì)繼續(xù)往下執(zhí)行。

5 、其它相關(guān)函數(shù)
  • dispatch_barrier_async和dispatch_barrier_sync(柵欄)

這兩個(gè)函數(shù)的作用差不多,都是把它前面和它后面的函數(shù)分隔開(kāi)。使它前面的任務(wù)先執(zhí)行,再執(zhí)行它添加的任務(wù),最后執(zhí)行它后面的任務(wù)。

那么它們有什么區(qū)別呢?
當(dāng)然從名字就能看出來(lái),就是提交任務(wù)的方式不同,一個(gè)是同步一個(gè)是異步,同步和異步的區(qū)別前面有解釋?zhuān)绻说脑?huà),可以再回去看看。

  • Dispatch Semaphore(信號(hào)量)

信號(hào)量其實(shí)就是用來(lái)保證訪(fǎng)問(wèn)資源的線(xiàn)程數(shù),當(dāng)信號(hào)量大于等于1時(shí),資源可以訪(fǎng)問(wèn),否則無(wú)法訪(fǎng)問(wèn)資源,直到其它線(xiàn)程釋放資源。

這里主要有三個(gè)函數(shù):

dispatch_semaphore_t dispatch_semaphore_create(long value);  //創(chuàng)建一個(gè)dispatch_semaphore_t,value為初始信號(hào)量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);   //信號(hào)量-1
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);   //信號(hào)量+1

怎么用呢?
還是舉個(gè)栗子吧:
假如有兩個(gè)資源,但是同時(shí)有三個(gè)線(xiàn)程想要訪(fǎng)問(wèn),就可以使用信號(hào)量進(jìn)行控制:

//crate的value表示,最多幾個(gè)資源可訪(fǎng)問(wèn)
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任務(wù)1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任務(wù)2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    //任務(wù)3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });

輸出如下:

2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860224] run task 1
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860221] run task 2
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860224] complete task 1
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860221] complete task 2
2017-09-27 18:04:28.591386+0800 aegewgr[5149:1860219] run task 3
2017-09-27 18:04:29.591845+0800 aegewgr[5149:1860219] complete task 3

假如把信號(hào)量設(shè)置為3呢?

 dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);

輸出如下:

2017-09-27 18:08:37.535634+0800 aegewgr[5169:1873722] run task 2
2017-09-27 18:08:37.535637+0800 aegewgr[5169:1873721] run task 1
2017-09-27 18:08:37.535636+0800 aegewgr[5169:1873723] run task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873723] complete task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873721] complete task 1
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873722] complete task 2
  • dispatch_once
    此函數(shù)在我們創(chuàng)建單例的時(shí)候經(jīng)常會(huì)用到,就是可以保證在應(yīng)用程序執(zhí)行中該函數(shù)只執(zhí)行一次。即使在多線(xiàn)程環(huán)境也,也可以保證百分百的安全。
寫(xiě)在文末

本來(lái)是打算寫(xiě)一篇關(guān)于多線(xiàn)程的文章的,因?yàn)橄氚袵CD介紹的全面一點(diǎn),所以篇幅就有點(diǎn)長(zhǎng)了,另外三種方式請(qǐng)看這里

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

推薦閱讀更多精彩內(nèi)容

  • GCD (Grand Central Dispatch) :iOS4 開(kāi)始引入,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,346評(píng)論 0 2
  • 簡(jiǎn)介 GCD(Grand Central Dispatch)是在macOS10.6提出來(lái)的,后來(lái)在iOS4.0被引...
    sunmumu1222閱讀 880評(píng)論 0 2
  • iOS中GCD的使用小結(jié) 作者dullgrass 2015.11.20 09:41*字?jǐn)?shù) 4996閱讀 20199...
    DanDanC閱讀 851評(píng)論 0 0
  • 本篇博客共分以下幾個(gè)模塊來(lái)介紹GCD的相關(guān)內(nèi)容: 多線(xiàn)程相關(guān)概念 多線(xiàn)程編程技術(shù)的優(yōu)缺點(diǎn)比較? GCD中的三種隊(duì)列...
    有夢(mèng)想的老伯伯閱讀 1,029評(píng)論 0 4
  • 我曾見(jiàn)到 那絳紫色的少年 渴望萬(wàn)丈光芒 卻痛惡著太陽(yáng)的灼傷 追光的人兒 那恐懼啊 欲蓋彌彰……
    青時(shí)_閱讀 66評(píng)論 0 3