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

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

  • pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueuef

這里我們主要講GCD

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

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

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

Dispatch Queue的種類

  • 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è)線程冕碟,因?yàn)樗谴嘘?duì)列拦惋,只會(huì)當(dāng)一個(gè)處理執(zhí)行完了才會(huì)將下一個(gè)任務(wù)交給線程處理。

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ù)提交給線程厕妖,但是因?yàn)椴恍枰却耙粋€(gè)任務(wù)執(zhí)行,所以幾乎是同時(shí)交給線程處理的挑庶。所以這里會(huì)使用多個(gè)線程言秸,而具體線程數(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)生死鎖的情況

我們直接在主線程中同步加入一個(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(主線程)中加入一個(gè)加入一個(gè)指定的Block难礼,并等待其執(zhí)行結(jié)束。而由于main_queue是一個(gè)串行隊(duì)列玫锋,它要等當(dāng)前線程中的任務(wù)處理完后才會(huì)把隊(duì)列中的任務(wù)提交到主線程蛾茉,而主線程又在等待這段代碼執(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)生死鎖的話一般都是在串行隊(duì)列中并且是在一個(gè)線程中同步往這個(gè)線程提交一個(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ù)沒有什么區(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)前線程直到group中的任務(wù)執(zhí)行完畢或者超過(guò)等待時(shí)間,才會(huì)繼續(xù)往下執(zhí)行锈颗。
dispatch_block_notify函數(shù)不會(huì)阻塞當(dāng)前線程顷霹,它只是指定了一個(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并沒有阻塞當(dāng)前線程覆醇,而且它提交的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)前線程排宰,只有當(dāng)group中的所有任務(wù)執(zhí)行完后線程才會(huì)繼續(xù)往下執(zhí)行似芝。

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

這兩個(gè)函數(shù)的作用差不多板甘,都是把它前面和它后面的函數(shù)分隔開党瓮。使它前面的任務(wù)先執(zhí)行,再執(zhí)行它添加的任務(wù)盐类,最后執(zhí)行它后面的任務(wù)寞奸。

那么它們有什么區(qū)別呢?
當(dāng)然從名字就能看出來(lái)在跳,就是提交任務(wù)的方式不同枪萄,一個(gè)是同步一個(gè)是異步,同步和異步的區(qū)別前面有解釋猫妙,如果忘了的話瓷翻,可以再回去看看。

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

信號(hào)量其實(shí)就是用來(lái)保證訪問(wèn)資源的線程數(shù)割坠,當(dāng)信號(hào)量大于等于1時(shí)齐帚,資源可以訪問(wèn),否則無(wú)法訪問(wè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è)線程想要訪問(wèn)摩瞎,就可以使用信號(hào)量進(jìn)行控制:

//crate的value表示,最多幾個(gè)資源可訪問(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í)行一次。即使在多線程環(huán)境也茫因,也可以保證百分百的安全。
寫在文末

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洛巢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子次兆,更是在濱河造成了極大的恐慌稿茉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芥炭,死亡現(xiàn)場(chǎng)離奇詭異漓库,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)园蝠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門渺蒿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人彪薛,你說(shuō)我怎么就攤上這事茂装。” “怎么了善延?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵少态,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我易遣,道長(zhǎng)彼妻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任训挡,我火速辦了婚禮澳骤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澜薄。我一直安慰自己为肮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布肤京。 她就那樣靜靜地躺著颊艳,像睡著了一般茅特。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棋枕,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天白修,我揣著相機(jī)與錄音,去河邊找鬼重斑。 笑死兵睛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窥浪。 我是一名探鬼主播祖很,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼漾脂!你這毒婦竟也來(lái)了假颇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤骨稿,失蹤者是張志新(化名)和其女友劉穎笨鸡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坦冠,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡形耗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蓝牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趟脂。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖例衍,靈堂內(nèi)的尸體忽然破棺而出昔期,到底是詐尸還是另有隱情,我是刑警寧澤佛玄,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布硼一,位于F島的核電站,受9級(jí)特大地震影響梦抢,放射性物質(zhì)發(fā)生泄漏般贼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一奥吩、第九天 我趴在偏房一處隱蔽的房頂上張望哼蛆。 院中可真熱鬧,春花似錦霞赫、人聲如沸腮介。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叠洗。三九已至甘改,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灭抑,已是汗流浹背十艾。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腾节,地道東北人忘嫉。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像禀倔,于是被迫代替她去往敵國(guó)和親榄融。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

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