本文摘錄自《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é)果: