iOS GCD全析(三)

本文摘錄自《Objective-C高級編程》一書,附加一些自己的理解偶芍,作為對GCD的總結(jié)。



此篇主要包含以下幾個方面:

  • Dispatch Group
    • dispatch_group_t
    • dispatch_group_create()
    • dispatch_group_async()
    • dispatch_group_notify()
    • dispatch_group_wait()
    • dispatch_group_enter() / dispatch_group_leave()
  • dispatch_barrier_async



Dispatch Group

當(dāng)我們向隊列中添加的多個任務(wù)全部執(zhí)行完畢后想最后再執(zhí)行一次“總結(jié)性”的任務(wù),這種需求經(jīng)常出現(xiàn)遣蚀。雖然我們可以交給串行隊列來處理,串行隊列方便我們控制其執(zhí)行順序纱耻,但是串行隊列不是多線程同時執(zhí)行芭梯,效率低。即使可以達(dá)到這種效果弄喘,源代碼也會變得頗為復(fù)雜玖喘。

在此種情況下使用Dispatch Group。例如下面源代碼為:追加3個Block到Global Dispatch Queue蘑志,這些Block如果全部執(zhí)行完畢累奈,就會執(zhí)行Main Dispatch Queue中結(jié)束處理用的Block贬派。


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(@"任務(wù)1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)3");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"done");
});

控制臺:

因為向Global Dispatch Queue 即 Concurrent Dispatch Queue追加處理,多個線程并行執(zhí)行费尽,所以追加處理的執(zhí)行順序不定。執(zhí)行時會發(fā)生變化羊始,但是此執(zhí)行結(jié)果的done一定是最后輸出的旱幼。

無論向什么樣的Dispatch Queue中追加處理,使用Dispatch Group都可監(jiān)視這些處理執(zhí)行的結(jié)束突委。一旦檢測到所有處理執(zhí)行結(jié)束柏卤,就可將"總結(jié)性"的處理追加到Dispatch Queue中。這就是使用Dispatch Group的原因匀油。

dispatch_group_async 函數(shù)與 dispatch_async 函數(shù)相同缘缚,都追加Block到指定的Dispatch Queue中。與 dispatch_async 函數(shù)不同的是指定生成的Dispatch Group為第一個參數(shù)敌蚜。指定的Block 屬于指定的Dispatch Group桥滨。

在追加到Dispatch Group中的處理全部執(zhí)行結(jié)束時,該源代碼中使用的dispatch_group_notify函數(shù)會將執(zhí)行的Block 追加到Dispatch Queue中弛车,將第一個參數(shù)指定為要監(jiān)視的Dispatch Group齐媒。在追加到該Dispatch Group的全部處理執(zhí)行結(jié)束時,將第三個參數(shù)的Block追加到第二個參數(shù)的Dispatch Queue中纷跛。在dispatch_group_notify函數(shù)中不管指定什么樣的Dispatch Queue喻括,屬于Dispatch Group的全部處理在追加指定的Block時都已執(zhí)行結(jié)束。

dispatch_group_wait

另外贫奠,在Dispatch Group 中也可以使用dispatch_group_wait函數(shù)僅等待全部處理執(zhí)行結(jié)束唬血。


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(@"任務(wù)1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)3");
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_async(group, queue, ^{
    NSLog(@"done");
});

控制臺:

dispatch_group_wait 函數(shù)的第二個參數(shù)指定為等待的時間(超時)。它屬于dispatch_ time_t 類型的值唤崭。該源代碼使用DISPATCH_TIME_FOREVER拷恨,意味著永久等待。只要屬于Dispatch Group 的處理尚未執(zhí)行結(jié)束谢肾,就會一直等待挑随,中途不能取消。

如果 dispatch_group_wait 函數(shù)的返回值不為0勒叠,就意味著雖然經(jīng)過了指定的時間兜挨,但屬于Dispatch Group的某一個處理還在執(zhí)行中。如果返回值為0眯分,那么全部處理執(zhí)行結(jié)束拌汇。當(dāng)?shù)却龝r間為DISPATCH_TIME_FOREVER、由dispatch_group_wait 函數(shù)返回時弊决,由于屬于Dispatch Group的處理必定全部執(zhí)行結(jié)束噪舀,因此返回值恒為0魁淳。

這里的“等待”是什么意思呢?這意味著一旦調(diào)用 dispatch_group_wait 函數(shù)与倡,該函數(shù)就處于調(diào)用的狀態(tài)而不返回界逛。即執(zhí)行dispatch_group_wait函數(shù)的現(xiàn)在的線程(當(dāng)前線程)停止。在經(jīng)過 dispatch_group_wait 函數(shù)中指定的時間或?qū)儆谥付―ispatch Group的處理全部執(zhí)行結(jié)束之前纺座,執(zhí)行該函數(shù)的線程停止息拜。

指定DISPATCH_TIME_NOW,則不用任何等待即可判定屬于Dispatch Group的處理是否執(zhí)行結(jié)束净响。

在主線程的RunLoop的每次循環(huán)中少欺,可檢查執(zhí)行是否結(jié)束,從而不耗費多余的等待時間馋贤,雖然這樣也可以赞别,但一般在這種情形下,還是推薦用 dispatch_group_notify 函數(shù)追加結(jié)束處理到Main Dispatch Queue中配乓。這是因為 dispatch_ group_notify 函數(shù)可以簡化源代碼仿滔。

對比 dispatch_ group_notify 函數(shù)和 dispatch_group_wait 函數(shù)的不同

首先是 dispatch_ group_notify 函數(shù)


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, ^{
    sleep(1);
    NSLog(@"任務(wù)1");
});

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"任務(wù)2");
});

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"任務(wù)3");
});

dispatch_group_notify(group, queue, ^{
    sleep(1);
    NSLog(@"任務(wù)4");
});

NSLog(@"任務(wù)5");

控制臺:

可以看出,任務(wù)5最先執(zhí)行犹芹,也就是說dispatch_group_notify函數(shù)是非線程阻塞的堤撵。

接下來看看 dispatch_group_wait 函數(shù)


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(@"任務(wù)1");
    sleep(1);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)2");
    sleep(1);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任務(wù)3");
    sleep(1);
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"任務(wù)5");

dispatch_group_async(group, queue, ^{
    NSLog(@"done");
});

控制臺:

可以看出,任務(wù)5是在dispatch_group_wait函數(shù)阻塞解除之后才執(zhí)行羽莺,所以dispatch_group_wait函數(shù)會阻塞線程实昨。

總結(jié):

  • dispatch_group_notify函數(shù)不會阻塞線程,dispatch_group_wait會阻塞線程

  • dispatch_group_notify 函數(shù)可以直接添加任務(wù)盐固,dispatch_group_wait函數(shù)只是單純的阻塞了線程繼續(xù)向下執(zhí)行荒给,所以需要自己另行添加“總結(jié)性任務(wù)”

  • dispatch_group_notify函數(shù)中的參數(shù)queue不論是否與dispatch_group_async是相同的queue隊列,都會最后執(zhí)行

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)4");
    });
    


dispatch_group_enter / dispatch_group_leave

先看看蘋果官方文檔關(guān)于dispatch_group_enter的描述:

Explicitly indicates that a block has entered the group.
顯式的表示一個代碼塊已經(jīng)加入到組中刁卜。

Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.
調(diào)用此函數(shù)將增加組中未完成任務(wù)的當(dāng)前計數(shù)志电。如果不使用dispatch_group_async函數(shù)進(jìn)行顯式的增加或者刪除組中的任務(wù),使用此函數(shù)(配合dispatch_group_leave函數(shù))可讓你的程序正確管理任務(wù)引用計數(shù)蛔趴。此函數(shù)的調(diào)用必須與dispatch_group_leave函數(shù)的調(diào)用相平衡挑辆。可以使用此函數(shù)將一個代碼塊與多個組同時關(guān)聯(lián)孝情。


再看看蘋果官方文檔關(guān)于dispatch_group_leave的描述:

Explicitly indicates that a block in the group has completed.
顯式的表示組中的一個代碼塊已經(jīng)執(zhí)行完畢鱼蝉。

Calling this function decrements the current count of outstanding tasks in the group. Using this function (with dispatch_group_enter) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function.
調(diào)用此函數(shù)將減少組中未完成任務(wù)的當(dāng)前計數(shù)。如果不使用dispatch_group_async函數(shù)進(jìn)行顯式的增加或者刪除組中的任務(wù)箫荡,使用此函數(shù)(配合dispatch_group_leave函數(shù))可允許你的程序正確管理任務(wù)引用計數(shù)魁亦。
?
A call to this function must balance a call to dispatch_group_enter. It is invalid to call it more times than dispatch_group_enter, which would result in a negative count.
此函數(shù)的調(diào)用必須與dispatch_group_enter函數(shù)的調(diào)用相平衡。調(diào)用它的次數(shù)超過dispatch_group_enter是無效的羔挡,這會導(dǎo)致負(fù)計數(shù)洁奈。


總的來說dispatch_group_enter / dispatch_group_leave這兩個函數(shù)在使用時是成對出現(xiàn)的间唉。enter是表示要向組中添加任務(wù)(代碼塊),組中未完成任務(wù)計數(shù) +1利术。leave表示組中有個任務(wù)完成了呈野,組中未完成任務(wù)計數(shù) -1。這兩個方法核心功能便是操作組中的任務(wù)計數(shù)印叁,而任務(wù)計數(shù)會影響到dispatch_group_wait函數(shù)和dispatch_group_notify函數(shù)被冒。

下面看個例子:


dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSLog(@"group task start");

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"任務(wù)1");
    sleep(3);
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任務(wù)全部結(jié)束!");
});

箭頭指示處就是打印的時間秒數(shù)喉钢,dispatch_group_notify中的任務(wù)的執(zhí)行時間比“任務(wù)1”晚了3秒姆打,可見dispatch_group_notify將“任務(wù)1”看做了組中的任務(wù)良姆。這就使得普通的異步函數(shù)也可以被加入到組中肠虽,方便做最后的“總結(jié)性”的處理。



dispatch_barrier_async

下面是頭文件中對dispatch_barrier_async函數(shù)的描述:

/*!
 * @functiongroup Dispatch Barrier API
 * The dispatch barrier API is a mechanism for submitting barrier blocks to a
 * dispatch queue, analogous to the dispatch_async()/dispatch_sync() API.
 * It enables the implementation of efficient reader/writer schemes.
 * Barrier blocks only behave specially when submitted to queues created with
 * the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block
 * will not run until all blocks submitted to the queue earlier have completed,
 * and any blocks submitted to the queue after a barrier block will not run
 * until the barrier block has completed.
 * When submitted to a a global queue or to a queue not created with the
 * DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to
 * blocks submitted with the dispatch_async()/dispatch_sync() API.
 */

大概意思就是說:這一系列的API只對通過dispatch_queue_create()創(chuàng)建出來的DISPATCH_QUEUE_CONCURRENT并發(fā)隊列有效玛追。如果是其它的隊列税课,比如全局隊列(global queue)或者所有不是通過dispatch_queue_create()創(chuàng)建出來的DISPATCH_QUEUE_CONCURRENT并發(fā)隊列,dispatch_barrier_async / dispatch_barrier_sync就會變成跟dispatch_async / dispatch_sync一樣痊剖,失去其特殊性韩玩,也就是說dispatch_barrier_async沒有“柵欄”的作用了。

下面舉一個例子來說明dispatch_barrier_async的具體功能:

// 通過dispatch_queue_create創(chuàng)建出來的DISPATCH_QUEUE_CONCURRENT并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);

__block int num = 10;

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)1 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)2 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)3 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)4 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)5 num=%d",num);
});

dispatch_barrier_async(queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"第%d次寫入", i+1);
    }
    num = 20;
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)6 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)7 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)8 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)9 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)10 num=%d",num);
});

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

我們由此可以看出陆馁,在dispatch_barrier_async之前添加到隊列的讀取任務(wù)1-5會并發(fā)執(zhí)行找颓,并且dispatch_barrier_async添加到隊列的寫入任務(wù)會等到這些前面的讀取任務(wù)1-5全部執(zhí)行完才會執(zhí)行。在執(zhí)行dispatch_barrier_async中的任務(wù)時叮贩,隊列中其它所有任務(wù)都會停下來等待它击狮,只有dispatch_barrier_async中的任務(wù)全部完成時,后面的任務(wù)才會繼續(xù)益老,而且依然是并發(fā)執(zhí)行彪蓬。

簡單來說,在dispatch_barrier_async之前添加進(jìn)去的任務(wù)會并發(fā)執(zhí)行捺萌,在dispatch_barrier_async之后添加進(jìn)去的也會并發(fā)執(zhí)行档冬,但是會被dispatch_barrier_async像柵欄一樣給隔開。在dispatch_barrier_async中的任務(wù)執(zhí)行時具有排他性桃纯,其它所有任務(wù)都得停下來等待它執(zhí)行完畢酷誓。這就為讀 / 寫保護(hù)提供了良好的工具,在寫入時沒有人來競爭資源态坦。


另外呛牲,dispatch_barrier_async的函數(shù)名中有async,我們會猜到應(yīng)該有另一個函數(shù)叫做dispatch_barrier_sync驮配,確實有這個函數(shù)娘扩,但是這個函數(shù)會造成線程阻塞着茸,像dispatch_group_wait一樣。

dispatch_barrier_async函數(shù)不會阻塞線程琐旁,所以主線程的任務(wù)不會受到阻礙

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

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)1");
});

dispatch_barrier_async(queue, ^{
    NSLog(@"寫入");
});

NSLog(@"主線程的任務(wù)");

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)2");
});

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

dispatch_barrier_sync函數(shù)會阻塞線程涮阔,所以主線程的任務(wù)會被阻擋到后面去

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

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)1");
});

dispatch_barrier_sync(queue, ^{
    NSLog(@"寫入");
});

NSLog(@"主線程的任務(wù)");

dispatch_async(queue, ^{
    NSLog(@"讀取任務(wù)2");
});

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市灰殴,隨后出現(xiàn)的幾起案子敬特,更是在濱河造成了極大的恐慌,老刑警劉巖牺陶,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伟阔,死亡現(xiàn)場離奇詭異,居然都是意外死亡掰伸,警方通過查閱死者的電腦和手機(jī)皱炉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狮鸭,“玉大人合搅,你說我怎么就攤上這事∑缃叮” “怎么了灾部?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惯退。 經(jīng)常有香客問我赌髓,道長,這世上最難降的妖魔是什么催跪? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任锁蠕,我火速辦了婚禮,結(jié)果婚禮上叠荠,老公的妹妹穿的比我還像新娘匿沛。我一直安慰自己,他們只是感情好榛鼎,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布逃呼。 她就那樣靜靜地躺著,像睡著了一般者娱。 火紅的嫁衣襯著肌膚如雪抡笼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天黄鳍,我揣著相機(jī)與錄音推姻,去河邊找鬼。 笑死框沟,一個胖子當(dāng)著我的面吹牛藏古,可吹牛的內(nèi)容都是我干的增炭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼拧晕,長吁一口氣:“原來是場噩夢啊……” “哼隙姿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厂捞,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤输玷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后靡馁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欲鹏,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年臭墨,在試婚紗的時候發(fā)現(xiàn)自己被綠了赔嚎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡裙犹,死狀恐怖尽狠,靈堂內(nèi)的尸體忽然破棺而出衔憨,到底是詐尸還是另有隱情叶圃,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布践图,位于F島的核電站掺冠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏码党。R本人自食惡果不足惜德崭,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揖盘。 院中可真熱鬧眉厨,春花似錦、人聲如沸兽狭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箕慧。三九已至服球,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颠焦,已是汗流浹背斩熊。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留伐庭,地道東北人粉渠。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓分冈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親霸株。 傳聞我的和親對象是個殘疾皇子丈秩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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