483,dispatch_apply和dispatch_once和dispatch_after的區(qū)別(面試點(diǎn):外部的異步執(zhí)行所在的隊(duì)列和 dispatch_apply 所在的隊(duì)列是同一個(gè)并發(fā)隊(duì)列...

一倔叼,dispatch_apply 的用法

當(dāng)我們需要進(jìn)行循環(huán)遍歷時(shí)肛度,例如遍歷一個(gè)數(shù)組郑现,我們一般會(huì)使用 For-In 循環(huán)夕春,F(xiàn)or-In 循環(huán)會(huì)從數(shù)組第一個(gè)元素開始依次循環(huán)遍歷到最后一個(gè)元素:

NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    
for (NSString *str in arr) {
    NSLog(@"str = %@", str);
}

// 打游椿摹:
/*
2019-09-23 21:23:21.387010+0800 GCDSummary[52959:1452344] str = a
2019-09-23 21:23:21.387119+0800 GCDSummary[52959:1452344] str = b
2019-09-23 21:23:21.387205+0800 GCDSummary[52959:1452344] str = c
2019-09-23 21:23:21.387287+0800 GCDSummary[52959:1452344] str = d
2019-09-23 21:23:21.387366+0800 GCDSummary[52959:1452344] str = e
*/

當(dāng) for 循環(huán)中的任務(wù)不多時(shí),我們可以直接使用這種方法遍歷某個(gè)容器及志,但是如果每一次循環(huán)都要執(zhí)行一個(gè)耗時(shí)操作的話該怎么辦呢片排,我們可能會(huì)立馬想到下邊的解決辦法:

// 在 for 循環(huán)中每一次任務(wù)都放到子線程中執(zhí)行
for (int i = 0; i < 1000; i++) {
    dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
        [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
        NSLog(@"%d -- %@", i, [NSThread currentThread]);
    });
}

這種辦法可取嗎寨腔?答案當(dāng)然是不可取率寡!如果每一次循環(huán)都要開啟一個(gè)子線程迫卢,那線程的無限制開啟將會(huì)耗費(fèi)非常大的資源,效率低下冶共,還可能造成應(yīng)用假死甚至程序崩潰乾蛤。

然而 GCD 給我們提供了一種快速迭代方法 dispatch_applydispatch_apply函數(shù)是dispatch_sync 函數(shù)和 Dispatch Group 的關(guān)聯(lián) API捅僵,該函數(shù)按照指定的次數(shù)將指定的任務(wù)追加到指定隊(duì)列中家卖,并等待全部任務(wù)執(zhí)行結(jié)束,系統(tǒng)會(huì)根據(jù)實(shí)際情況自動(dòng)分配和管理線程庙楚。

我們使用 dispatch_apply方法模擬上面的for循環(huán)任務(wù):

dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
    dispatch_apply(1000, dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^(size_t index) {
        [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
        NSLog(@"%zu -- %@", index, [NSThread currentThread]);
    });
});

兩種方法運(yùn)行后查看打印信息(可自行測(cè)試)上荡,可以看到,使用 dispatch_apply 方法循環(huán)打印還沒有直接在 for 循環(huán)中不斷開啟線程速度快馒闷。但是我們應(yīng)當(dāng)注意酪捡,for 循環(huán)中不斷開啟線程造成了大量線程的開啟(模擬器測(cè)試開啟了70個(gè)左右的子線程),根本無法顧忌系統(tǒng)性能纳账,而dispatch_apply方法則可以智能管理線程沛善,始終是五六個(gè)子線程循環(huán)使用,這也是導(dǎo)致速度較慢的原因塞祈。實(shí)際開發(fā)中如果不考慮系統(tǒng)性能肆意開啟子線程,可能會(huì)出現(xiàn)線程擁堵(假死)帅涂、程序崩潰的情況议薪。

下面,我們對(duì)上面例子中的數(shù)組使用 dispatch_apply 方法進(jìn)行迭代:

NSLog(@"-- begin --");
    
NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    
dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);

/*
arr.count    指定重復(fù)次數(shù)  這里數(shù)組內(nèi)元素個(gè)數(shù)是 5 個(gè)媳友,也就是重復(fù) 5 次
queue        追加任務(wù)的隊(duì)列
index        帶有參數(shù)的 Block, index 的作用是為了按執(zhí)行的順序區(qū)分各個(gè) Block
*/
dispatch_apply(arr.count, queue, ^(size_t index) {
    NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
});

NSLog(@"-- end --");

運(yùn)行后查看打印信息:

2019-09-23 22:28:53.914873+0800 GCDSummary[53815:1472829] -- begin --
2019-09-23 22:28:53.915211+0800 GCDSummary[53815:1472880] index = 3, str = d -- <NSThread: 0x600002761780>{number = 5, name = (null)}
2019-09-23 22:28:53.915212+0800 GCDSummary[53815:1472882] index = 2, str = c -- <NSThread: 0x600002765240>{number = 4, name = (null)}
2019-09-23 22:28:53.915211+0800 GCDSummary[53815:1472883] index = 1, str = b -- <NSThread: 0x60000275fb80>{number = 3, name = (null)}
2019-09-23 22:28:53.915216+0800 GCDSummary[53815:1472829] index = 0, str = a -- <NSThread: 0x600002732900>{number = 1, name = main}
2019-09-23 22:28:53.915350+0800 GCDSummary[53815:1472880] index = 4, str = e -- <NSThread: 0x600002761780>{number = 5, name = (null)}
2019-09-23 22:28:53.915476+0800 GCDSummary[53815:1472829] -- end --

可以看到斯议,dispatch_apply 方法中的任務(wù)是并發(fā)執(zhí)行的,但是阻塞了當(dāng)前線程(主線程)醇锚,當(dāng)所有任務(wù)執(zhí)行完畢后才打印了“-- end --”哼御。所以在我們使用dispatch_apply 方法時(shí),應(yīng)當(dāng)在外邊套上一層 dispatch_async焊唬。

另外恋昼,當(dāng) index 等于 0 的時(shí)候(第一個(gè)任務(wù)執(zhí)行時(shí)),任務(wù)一定是在當(dāng)前線程(該例中的當(dāng)前線程是主線程)中執(zhí)行赶促,這一點(diǎn)可以理解為除第一個(gè)任務(wù)的其他任務(wù)都是依照和第一個(gè)任務(wù)并發(fā)的目的放進(jìn)了并發(fā)隊(duì)列里液肌。

注意dispatch_apply 方法非常適合大量字典數(shù)據(jù)轉(zhuǎn)模型

有一個(gè)現(xiàn)象需要注意鸥滨,先看代碼:

NSLog(@"-- begin --");

NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];

dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"current thread -- %@", [NSThread currentThread]);
    
    dispatch_apply(arr.count, queue, ^(size_t index) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
    });

});

NSLog(@"-- end --");

運(yùn)行后查看打印信息:

2019-09-24 01:04:59.061687+0800 GCDSummary[56119:1532552] -- begin --
2019-09-24 01:04:59.061909+0800 GCDSummary[56119:1532552] -- end --
2019-09-24 01:04:59.062015+0800 GCDSummary[56119:1532600] current thread -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}
2019-09-24 01:05:01.062637+0800 GCDSummary[56119:1532600] index = 0, str = a -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}
2019-09-24 01:05:03.065916+0800 GCDSummary[56119:1532600] index = 1, str = b -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}
2019-09-24 01:05:05.068437+0800 GCDSummary[56119:1532600] index = 2, str = c -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}
2019-09-24 01:05:07.069896+0800 GCDSummary[56119:1532600] index = 3, str = d -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}
2019-09-24 01:05:09.075196+0800 GCDSummary[56119:1532600] index = 4, str = e -- <NSThread: 0x600001940dc0>{number = 3, name = (null)}

可以看到嗦哆,外部的異步執(zhí)行所在的隊(duì)列和 dispatch_apply 所在的隊(duì)列是同一個(gè)并發(fā)隊(duì)列谤祖。由于整個(gè) dispatch_apply 方法被放在了異步執(zhí)行中,所以所有任務(wù)都在子線程中執(zhí)行老速。

需要注意的是:子線程卻只開啟了一個(gè)粥喜,在 dispatch_apply方法中各個(gè)任務(wù)所在的子線程和外部異步執(zhí)行所在的子線程是同一個(gè),這也就導(dǎo)致了 5 次打印依次間隔 2 秒執(zhí)行(無法開啟更多線程支持并發(fā)執(zhí)行任務(wù))橘券。

經(jīng)過多次試驗(yàn)额湘,發(fā)現(xiàn)問題出在這個(gè)并發(fā)隊(duì)列 dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT); 身上,當(dāng)我們使用 dispatch_queue_create 方法創(chuàng)建并發(fā)隊(duì)列并在內(nèi)外兩層都使用這個(gè)隊(duì)列時(shí)约郁,任務(wù)的執(zhí)行就變成了串行執(zhí)行(只開啟了一個(gè)子線程并且只能用這個(gè)子線程執(zhí)行任務(wù))缩挑。

這里還有一個(gè)情況:如果我們內(nèi)外兩層都使用 dispatch_get_global_queue 方法來獲取并行隊(duì)列,即上述代碼第三行修改為 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 的話鬓梅,打印信息如下:

2019-09-24 01:13:28.157639+0800 GCDSummary[56241:1536440] -- begin --
2019-09-24 01:13:28.157839+0800 GCDSummary[56241:1536440] -- end --
2019-09-24 01:13:28.157902+0800 GCDSummary[56241:1536489] current thread -- <NSThread: 0x600002fc5c80>{number = 3, name = (null)}
2019-09-24 01:13:30.163310+0800 GCDSummary[56241:1536487] index = 2, str = c -- <NSThread: 0x600002fcecc0>{number = 5, name = (null)}
2019-09-24 01:13:30.163373+0800 GCDSummary[56241:1536488] index = 1, str = b -- <NSThread: 0x600002fcea80>{number = 4, name = (null)}
2019-09-24 01:13:30.163345+0800 GCDSummary[56241:1536490] index = 3, str = d -- <NSThread: 0x600002ff8f40>{number = 6, name = (null)}
2019-09-24 01:13:30.163314+0800 GCDSummary[56241:1536489] index = 0, str = a -- <NSThread: 0x600002fc5c80>{number = 3, name = (null)}
2019-09-24 01:13:32.164862+0800 GCDSummary[56241:1536488] index = 4, str = e -- <NSThread: 0x600002fcea80>{number = 4, name = (null)}

可以看到供置,使用 dispatch_get_global_queue 的話,所有任務(wù)可以正常并發(fā)執(zhí)行绽快,這是為什么呢芥丧?

首先看一下兩種并發(fā)隊(duì)列的區(qū)別:

Global queues 是全局并發(fā)隊(duì)列,由整個(gè)進(jìn)程共享坊罢。進(jìn)程中存在三個(gè)全局隊(duì)列:高续担、中(默認(rèn))、低三個(gè)優(yōu)先級(jí)隊(duì)列活孩∥镉觯可以調(diào)用 dispatch_get_global_queue 函數(shù)傳入優(yōu)先級(jí)來訪問隊(duì)列。

dispatch_queue_create 創(chuàng)建出來的是用戶隊(duì)列憾儒,由用戶通過 dispatch_queue_create 函數(shù)自行創(chuàng)建询兴。

我們發(fā)現(xiàn)全局并發(fā)隊(duì)列是一直存在的,我們調(diào)用 dispatch_get_global_queue 函數(shù)只是獲得了對(duì)其的訪問使用權(quán)起趾,指定優(yōu)先級(jí)可以向系統(tǒng)說明我們要在哪個(gè)級(jí)別的全局并發(fā)隊(duì)列中處理任務(wù)诗舰,而 dispatch_queue_create 函數(shù)是創(chuàng)建了自定義隊(duì)列。

造成上述情況的原因有可能是因?yàn)槿植l(fā)隊(duì)列處理任務(wù)的底層邏輯和自定義并發(fā)隊(duì)列不同训裆,因?yàn)檎麄€(gè)進(jìn)程可以共享全局并發(fā)隊(duì)列眶根,所以其開啟子線程或者利用(分配)已開啟的線程的能力(權(quán)限)更強(qiáng)大,可以為內(nèi)外兩層函數(shù)開啟不同的子線程边琉。

特別注意:如下兩種情況會(huì)造成死鎖:
// 1属百、在主線程中使用主線程執(zhí)行 dispatch_apply 方法
dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
    NSLog(@"%zu", index);
});
// 2、在串行隊(duì)列中使用該串行隊(duì)列執(zhí)行 dispatch_apply 方法
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });
});

前邊有提到变姨,主線程隊(duì)列是一種特殊的串行隊(duì)列诸老,所以說是兩種情況,其實(shí)可以歸結(jié)為一種:當(dāng)在串行隊(duì)列中執(zhí)行 dispatch_apply 方法,且 dispatch_apply 方法使用的也是這個(gè)串行隊(duì)列時(shí)别伏,會(huì)造成死鎖蹄衷。

二,dispatch_after 的用法

dispatch_after是來延遲執(zhí)行的GCD方法厘肮,因?yàn)樵谥骶€程中我們不能用sleep來延遲方法的調(diào)用愧口,所以用dispatch_after是最合適的

dispatch_after能讓我們添加進(jìn)隊(duì)列的任務(wù)延時(shí)執(zhí)行,該函數(shù)并不是在指定時(shí)間后執(zhí)行處理类茂,而只是在指定時(shí)間追加處理到dispatch_queue

代碼實(shí)現(xiàn)

//該方法的第一個(gè)參數(shù)是time耍属,第二個(gè)參數(shù)是dispatch_queue,第三個(gè)參數(shù)是要執(zhí)行的block巩检。
    //在主線程中延遲執(zhí)行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });

特別注意

上面這句dispatch_after的真正含義是在6秒后把任務(wù)添加進(jìn)隊(duì)列中厚骗,并不是表示在6秒后執(zhí)行,大部分情況該函數(shù)能達(dá)到我們的預(yù)期兢哭,只有在對(duì)時(shí)間要求非常精準(zhǔn)的情況下才可能會(huì)出現(xiàn)問題领舰。

三,dispatch_once 只使用一次

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迟螺,一起剝皮案震驚了整個(gè)濱河市冲秽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矩父,老刑警劉巖锉桑,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異窍株,居然都是意外死亡民轴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門球订,熙熙樓的掌柜王于貴愁眉苦臉地迎上來后裸,“玉大人,你說我怎么就攤上這事辙售。” “怎么了飞涂?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵旦部,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我较店,道長(zhǎng)士八,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任梁呈,我火速辦了婚禮婚度,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘官卡。我一直安慰自己蝗茁,他們只是感情好醋虏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哮翘,像睡著了一般颈嚼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饭寺,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天阻课,我揣著相機(jī)與錄音,去河邊找鬼艰匙。 笑死限煞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的员凝。 我是一名探鬼主播署驻,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼绊序!你這毒婦竟也來了硕舆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤骤公,失蹤者是張志新(化名)和其女友劉穎抚官,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阶捆,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凌节,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倍奢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垒棋。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叼架,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乖订,到底是詐尸還是另有隱情,我是刑警寧澤甜无,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布岂丘,位于F島的核電站,受9級(jí)特大地震影響元潘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牲距,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一牍鞠、第九天 我趴在偏房一處隱蔽的房頂上張望评姨。 院中可真熱鬧,春花似錦胁后、人聲如沸嗦枢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氧秘。三九已至,卻和暖如春搔确,著一層夾襖步出監(jiān)牢的瞬間灭忠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工畦幢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缆蝉,地道東北人刊头。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓原杂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親穿肄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 一、dispatch_after 功能:延遲一段時(shí)間把一項(xiàng)任務(wù)提交到隊(duì)列中執(zhí)行脑溢,返回之后就不能取消常用來在在主隊(duì)列...
    HuaJianDev閱讀 576評(píng)論 0 1
  • 一验庙、dispatch_group 有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行兩個(gè)耗時(shí)任務(wù),當(dāng)兩個(gè)耗時(shí)任務(wù)都完成以后回到...
    司空123閱讀 1,546評(píng)論 0 3
  • GCD調(diào)度隊(duì)列是執(zhí)行任務(wù)的強(qiáng)大工具社牲。調(diào)度隊(duì)列允許您相對(duì)于調(diào)度者異步或者同步的執(zhí)行任意代碼塊。您能夠使用調(diào)度隊(duì)列來執(zhí)...
    坤坤同學(xué)閱讀 6,674評(píng)論 1 3
  • GCD全程Grand Central Dispath汗菜,是蘋果提供的一套多核并行運(yùn)算的解決方案挑社,GCD使用純C語(yǔ)言的...
    iOSer_jia閱讀 133評(píng)論 0 0
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)痛阻、焦點(diǎn)、注意力俏扩、語(yǔ)言聯(lián)想、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析录淡,社會(huì)...
    Jenaral閱讀 5,721評(píng)論 0 5