GCD探究(一) -- 任務(wù)與隊(duì)列

GCD全程Grand Central Dispath,是蘋果提供的一套多核并行運(yùn)算的解決方案什湘,GCD使用純C語言的API,提供了非常強(qiáng)大的API,它會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核涣澡、四核),自動(dòng)管理線程的生命周期(創(chuàng)建線程丧诺、調(diào)度線程入桂、銷毀線程),我們只需要告訴GCD想要執(zhí)行什么任務(wù)锅必,不需要編寫任何線程管理代碼事格。

函數(shù)、隊(duì)列與任務(wù)

GCD常見的用法

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"GCD");
});

可以看到GCD的使用分為3個(gè)部分搞隐,函數(shù)dispatch_sync驹愚、隊(duì)列dispatch_get_global_queue(0, 0),以及任務(wù)block劣纲。

函數(shù)

在GCD中可以將執(zhí)行函數(shù)分為同步和異步兩種

  • 同步函數(shù):dispatch_sync逢捺,必須等待當(dāng)前任務(wù)block執(zhí)行完畢后才能執(zhí)行下一語句,同步函數(shù)不會(huì)開啟線程癞季,會(huì)在當(dāng)前線程中執(zhí)行任務(wù)
  • 異步函數(shù):dispatch_async劫瞳,不用等待當(dāng)前任務(wù)block執(zhí)行完畢就可以執(zhí)行下一語句,可以開啟線程執(zhí)行線程

隊(duì)列

GCD隊(duì)列是任務(wù)的等待隊(duì)列绷柒,遵循先進(jìn)先出(FIFO)原則志于,沒調(diào)度一個(gè)任務(wù)就從隊(duì)列中釋放一個(gè)任務(wù),隊(duì)列有兩種:串行隊(duì)列和并發(fā)隊(duì)列废睦,兩者的主要區(qū)別為執(zhí)行順序不同伺绽,開啟線程數(shù)不同

串行隊(duì)列:每次只能有一個(gè)任務(wù)執(zhí)行,任務(wù)一個(gè)接著一個(gè)執(zhí)行嗜湃,一個(gè)任務(wù)執(zhí)行完畢后奈应,在執(zhí)行下一個(gè)任務(wù)

dispatch_queue_t queue = dispatch_queue_create("net.gcd.queue", DISPATCH_QUEUE_SERIAL);

并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)執(zhí)行,可以開啟多想線程

dispatch_queue_t queue = dispatch_queue_create("net.gcd.queue", DISPATCH_QUEUE_CONCURRENT);

并發(fā)隊(duì)列的并發(fā)功能只有在異步函數(shù)下才有效
#define DISPATCH_QUEUE_SERIAL NULL,所以dispatch_queue_create("", NULL)得到的是同步隊(duì)列

主隊(duì)列

GCD提供了一種特殊的串行隊(duì)列--主隊(duì)列购披,所有放在隊(duì)列中的任務(wù)都會(huì)放到主隊(duì)列中執(zhí)行

全局并發(fā)隊(duì)列

GCD默認(rèn)提供了全局并發(fā)隊(duì)列杖挣,他需要提供兩個(gè)參數(shù),第一個(gè)是隊(duì)列優(yōu)先級(jí)刚陡,通常用DISPATCH_QUEUE_PRIORITY_DEFAULT,在iOS9之后惩妇,已經(jīng)被服務(wù)質(zhì)量代替株汉,如QOS_CLASS_DEFAULT,第二個(gè)參數(shù)是蘋果的保留字段屿附,一般寫0.

dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

在使用多線程開發(fā)時(shí)郎逃,如果對(duì)隊(duì)列沒有特殊需求,可以直接使用全局并發(fā)隊(duì)列

函數(shù)與隊(duì)列

根據(jù)函數(shù)與隊(duì)列的兩兩組合挺份,會(huì)出現(xiàn)出現(xiàn)以下四種結(jié)果

同步函數(shù)并發(fā)隊(duì)列

  • 不會(huì)開啟線程褒翰,在當(dāng)前線程執(zhí)行任務(wù)
  • 任務(wù)串行執(zhí)行,任務(wù)一個(gè)接著一個(gè)
  • 可能會(huì)產(chǎn)生堵塞

同步函數(shù)并發(fā)隊(duì)列

  • 不會(huì)開啟線程匀泊,在當(dāng)前線程執(zhí)行任務(wù)
  • 任務(wù)一個(gè)接著一個(gè)

異步函數(shù)串行隊(duì)列

  • 開啟一條新線程
  • 任務(wù)一個(gè)接著一個(gè)

異步函數(shù)并發(fā)隊(duì)列

  • 開啟多個(gè)線程
  • 異步執(zhí)行任務(wù)优训,沒有順序,與CPU調(diào)度有關(guān)

GCD的其他方法

除了開啟線程的能力各聘,GCD還提供了許多api揣非,我們可以利用這些api實(shí)現(xiàn)單例、多讀單寫躲因、計(jì)時(shí)器等功能

柵欄函數(shù) dispatch_barrier_async

如果我們需要異步執(zhí)行兩組操作早敬,而且第一組操作執(zhí)行完成之后才能執(zhí)行第二組操作,我們就需要一個(gè)柵欄一樣的東西將這兩組操作分割開來大脉,而GCD的dispatch_barrier_async便可以實(shí)現(xiàn)著這一功能搞监。

dispatch_barrier_async會(huì)等待前邊追加到并發(fā)隊(duì)列的任務(wù)全部執(zhí)行完畢后,再將指定的任務(wù)追加到該異步隊(duì)列中镰矿,然后在dispatch_barrier_async追加的任務(wù)執(zhí)行完畢之后琐驴,異步隊(duì)列才恢復(fù)為正常執(zhí)行矗积。

例如:

dispatch_queue_t queue = dispatch_queue_create("barrier_test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1");
    }
});
    
dispatch_async(queue, ^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2");
    }
});
    
dispatch_barrier_async(queue, ^{
   for (int i = 0; i < 2; i++) {
       NSLog(@"barrier");
   }
});
    
dispatch_async(queue, ^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3");
    }
});
    
dispatch_async(queue, ^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4");
    }
});

執(zhí)行結(jié)果為

2020-11-25 16:05:29.664252+0800 GCDTest[91450:4599536] 1
2020-11-25 16:05:29.664294+0800 GCDTest[91450:4599535] 2
2020-11-25 16:05:31.668346+0800 GCDTest[91450:4599535] 2
2020-11-25 16:05:31.668442+0800 GCDTest[91450:4599536] 1
2020-11-25 16:05:31.668615+0800 GCDTest[91450:4599536] barrier
2020-11-25 16:05:31.668735+0800 GCDTest[91450:4599536] barrier
2020-11-25 16:05:33.670656+0800 GCDTest[91450:4599536] 3
2020-11-25 16:05:33.670617+0800 GCDTest[91450:4599535] 4
2020-11-25 16:05:35.675116+0800 GCDTest[91450:4599535] 4
2020-11-25 16:05:35.675144+0800 GCDTest[91450:4599536] 3

利用這一特性梁只,我們可以使用柵欄函數(shù)實(shí)現(xiàn)多讀單寫。

- (void)setName:(NSString *)name {
    dispatch_barrier_async(self.queue, ^{
        self->_name = [name copy];
    });
}

- (NSString *)name {
    __block NSString *tempName;
    dispatch_sync(self.queue, ^{
        tempName = self->_name;
    });
    return tempName;
}

- (dispatch_queue_t)queue {
    if (!_queue) {
        _queue = dispatch_queue_create("barrier_test", DISPATCH_QUEUE_CONCURRENT);
    }
    return _queue;
}

隊(duì)列組 dispatch_group

有時(shí)候我們會(huì)有這樣的需求锄贷,分別執(zhí)行兩個(gè)異步耗時(shí)任務(wù)苍姜,然后當(dāng)兩個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)牢酵,這時(shí)候可以用到GCD的隊(duì)列組。

GCD的隊(duì)列組有幾個(gè)關(guān)鍵的api

  • dispatch_group_create 創(chuàng)建隊(duì)列組
  • dispatch_group_enter 加入隊(duì)列組
  • dispatch_group_leave 離開隊(duì)列組
  • dispatch_group_async 將任務(wù)加入隊(duì)列組
  • dispatch_group_notify 回到指定隊(duì)列執(zhí)行任務(wù)
  • dispatch_group_wait 同上衙猪,但會(huì)阻塞當(dāng)前線程
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"2");
});
dispatch_group_notify(group, queue, ^{
    NSLog(@"3");
});

執(zhí)行結(jié)果:

2020-11-26 17:33:44.591118+0800 GCDTest[6468:5517558] 2
2020-11-26 17:33:44.591118+0800 GCDTest[6468:5517552] 1
2020-11-26 17:33:44.591338+0800 GCDTest[6468:5517552] 3

GCD一次性代碼 dispatch_once

我們?cè)趧?chuàng)建單例茁帽、或者有整個(gè)運(yùn)行過程中只執(zhí)行一次的代碼時(shí),我們可以使用GCD的dispatch_once實(shí)現(xiàn)屈嗤,即使在多線程的環(huán)境,dispatch_once也可以保證線程安全吊输。

- (void)once {
    static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
        // 只執(zhí)行一次的代碼
    });
}

GCD計(jì)時(shí)器 dispatch_source

dispatch source 是一種處理事件的數(shù)據(jù)類型饶号,這些被處理的事件為操作系統(tǒng)中的底層級(jí)別,而計(jì)時(shí)器類型是其支持的其中一種類型季蚂。

_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
    NSLog(@"source");
});
dispatch_resume(_timer);
  • dispatch_source_create :創(chuàng)建source茫船,計(jì)時(shí)器類型使用DISPATCH_SOURCE_TYPE_TIMER琅束,而且可以指定隊(duì)列
  • dispatch_source_set_timer:設(shè)置定時(shí)器的相關(guān)參數(shù),第一個(gè)參數(shù)為定時(shí)器算谈,第二個(gè)為開始時(shí)間涩禀,第三個(gè)為回調(diào)時(shí)間間隔,第四個(gè)允許誤差范圍
  • dispatch_source_set_event_handler:設(shè)置事件處理句柄然眼,一個(gè)句柄可以是一個(gè)block或者是一個(gè)函數(shù)艾船,dispatch source會(huì)把句柄投放到隊(duì)列中執(zhí)行。
  • dispatch_resume:timer默認(rèn)是掛起的高每,需要手動(dòng)開啟

NSTimer相比屿岂,GCD的timer不需要依賴runloop,不會(huì)因?yàn)閞unloop的繁忙而導(dǎo)致及時(shí)不準(zhǔn)鲸匿。

GCD快速迭代方法 dispatch_apply

和for循環(huán)類似爷怀,dispatch_apply可以快速循環(huán)遍歷。而相比于普通的for循環(huán)带欢,dispatch_apply按照指定次數(shù)將任務(wù)追加到指定的隊(duì)列中运授,并等待全部隊(duì)列執(zhí)行結(jié)束。

如果在串行隊(duì)列中使用dispatch_apply乔煞,那么就和for循環(huán)一樣按照順序同步執(zhí)行吁朦,沒有快速迭代的意義,我們可以利用并發(fā)隊(duì)列隊(duì)形進(jìn)行異步執(zhí)行瘤缩,讓dispatch_apply的任務(wù)多個(gè)線程中不是異步執(zhí)行喇完,并且無論在串行隊(duì)列還是異步隊(duì)列,dispatch_apply都會(huì)等到全部任務(wù)執(zhí)行完成剥啤,有點(diǎn)類似dispatch_group_wait方法锦溪。

可以用下面的例子清楚了解:

NSLog(@"start");
dispatch_apply(10, queue, ^(size_t i) {
    NSLog(@"%zd-----%@", i, [NSThread currentThread]);
});
NSLog(@"end");

執(zhí)行結(jié)果為:

2020-11-27 15:35:23.341807+0800 GCDTest[44897:5974416] start
2020-11-27 15:35:23.341930+0800 GCDTest[44897:5974416] 0-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.341998+0800 GCDTest[44897:5974416] 2-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342015+0800 GCDTest[44897:5974466] 1-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342048+0800 GCDTest[44897:5974416] 3-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342096+0800 GCDTest[44897:5974416] 5-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342093+0800 GCDTest[44897:5974466] 4-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342180+0800 GCDTest[44897:5974466] 7-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342188+0800 GCDTest[44897:5974416] 6-----<NSThread: 0x600001700040>{number = 1, name = main}
2020-11-27 15:35:23.342316+0800 GCDTest[44897:5974466] 8-----<NSThread: 0x60000173a500>{number = 2, name = (null)}
2020-11-27 15:35:23.342346+0800 GCDTest[44897:5974464] 9-----<NSThread: 0x60000174e880>{number = 3, name = (null)}
2020-11-27 15:35:23.342849+0800 GCDTest[44897:5974416] end

GCD信號(hào)量 dispatch_semaphore

GCD中的信號(hào)量指的是dispatch_semaphore,是持有計(jì)數(shù)的信號(hào),類似高速路收費(fèi)站的欄桿府怯,可以通過時(shí)刻诊,打開欄桿,不可以通過時(shí)牺丙,關(guān)閉欄桿则涯。在dispatch_semaphore中,使用計(jì)數(shù)來完成這個(gè)功能冲簿,計(jì)數(shù)為0時(shí)等待粟判,不可通過,計(jì)數(shù)為1或大于1時(shí)峦剔,計(jì)數(shù)減1且不等待档礁,可通過。

dispatch_semaphore提供了三個(gè)函數(shù)吝沫。

  • dispatch_semaphore_create:創(chuàng)建一個(gè)Semaphore并初始化信號(hào)的總量
  • dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào)呻澜,讓信號(hào)總量加1
  • dispatch_semaphore_wait:可以使總信號(hào)量減1递礼,當(dāng)信號(hào)量為0時(shí)就會(huì)一直等待(阻塞所有線程),否則可以正常執(zhí)行

基于dispatch_semaphore的這一性質(zhì)羹幸,它常常被用來作為鎖來實(shí)現(xiàn)線程同步脊髓,如下

NSLog(@"current thread: %@", [NSThread currentThread]);
    
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
__block int num = 0;
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"current thread: %@", [NSThread currentThread]);
    num = 100;
    dispatch_semaphore_signal(semaphore);
});
    
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"num = %d", num);

如果沒有加入dispatch_semaphore,那么NSLog(@"num = %d", num);便不會(huì)等待num = 100;執(zhí)行完成栅受,而是直接打印當(dāng)前數(shù)值0将硝,而加入dispatch_semaphore_t后,開始semaphore為0窘疮,執(zhí)行到
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);時(shí)袋哼,因?yàn)?code>semaphore為0,所以下面的代碼不會(huì)執(zhí)行闸衫,而子線程中的任務(wù)執(zhí)行num = 100;完成后涛贯,會(huì)調(diào)用一遍dispatch_semaphore_signal(semaphore);semaphore加一變?yōu)?,此時(shí)會(huì)通知到dispatch_semaphore蔚出,從而繼續(xù)往下執(zhí)行NSLog(@"num = %d", num);弟翘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骄酗,隨后出現(xiàn)的幾起案子稀余,更是在濱河造成了極大的恐慌,老刑警劉巖趋翻,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睛琳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踏烙,警方通過查閱死者的電腦和手機(jī)师骗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讨惩,“玉大人辟癌,你說我怎么就攤上這事〖瞿恚” “怎么了黍少?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)处面。 經(jīng)常有香客問我厂置,道長(zhǎng),這世上最難降的妖魔是什么魂角? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任农渊,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砸紊。我一直安慰自己,他們只是感情好囱挑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布醉顽。 她就那樣靜靜地躺著,像睡著了一般平挑。 火紅的嫁衣襯著肌膚如雪游添。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天通熄,我揣著相機(jī)與錄音唆涝,去河邊找鬼。 笑死唇辨,一個(gè)胖子當(dāng)著我的面吹牛廊酣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赏枚,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼亡驰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了饿幅?” 一聲冷哼從身側(cè)響起凡辱,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栗恩,沒想到半個(gè)月后透乾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磕秤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年乳乌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亲澡。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钦扭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出床绪,到底是詐尸還是另有隱情客情,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布癞己,位于F島的核電站膀斋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痹雅。R本人自食惡果不足惜仰担,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绩社。 院中可真熱鬧摔蓝,春花似錦赂苗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猜谚,卻和暖如春败砂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魏铅。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工昌犹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人览芳。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓斜姥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親路操。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疾渴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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