原文發(fā)表于humancode.us元莫,地址是GCD Target Queues扳剿,由我們團(tuán)隊(duì)的井小二童鞋(美女哦)翻譯完成宪郊。該文已得到原文作者的翻譯許可峡扩。
這是關(guān)于GCD系列的第四篇文章蹭越。
跟我一起放慢腳步,看一下GCD的一個實(shí)用功能:目標(biāo)隊(duì)列(target queues)教届。
開始旅程之前响鹃,我們先來學(xué)習(xí)下一個特殊的隊(duì)列:全局并發(fā)隊(duì)列。
全局并發(fā)隊(duì)列
GCD提供了四種在程序中一直有效的全局并發(fā)隊(duì)列案训。這些隊(duì)列非常特殊买置,因?yàn)樗麄冇上到y(tǒng)自動創(chuàng)建,能夠一直運(yùn)行强霎,并且能夠像處理普通的block一樣處理barrier block忿项。因?yàn)檫@些隊(duì)列是并發(fā)的,所以所有入隊(duì)的block將并行運(yùn)行城舞。
這四種全局并發(fā)隊(duì)列有不同的優(yōu)先級:
- DISPATCH_QUEUE_PRIORITY_HIGH
- DISPATCH_QUEUE_PRIORITY_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
高優(yōu)先級隊(duì)列中的block會搶占低優(yōu)先級隊(duì)列中的block的資源轩触。
這些全局并發(fā)隊(duì)列在GCD中扮演了線程優(yōu)先級的角色。像線程一樣家夺,高優(yōu)先級隊(duì)列中執(zhí)行的block可能會消耗所有CPU的資源脱柱,而使低優(yōu)先級的隊(duì)列卻處于“饑餓”狀態(tài),并阻止了入隊(duì)低優(yōu)先級隊(duì)列的block的執(zhí)行秦踪。
通過下面的方法褐捻,你能得到一個全局并發(fā)隊(duì)列:
dispatch_queue_t defaultPriorityGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
目標(biāo)隊(duì)列
那么如何開始使用這些全局并發(fā)隊(duì)列呢?令人驚訝的答案是:你已經(jīng)在使用它們椅邓!你創(chuàng)建的任何隊(duì)列都包含一個目標(biāo)隊(duì)列柠逞。默認(rèn)情況下,這些隊(duì)列的目標(biāo)隊(duì)列是優(yōu)先級為DISPATCH_QUEUE_PRIORITY_DEFAULT
的全局隊(duì)列景馁。
一個隊(duì)列有一個目標(biāo)隊(duì)列是什么意思呢板壮?事實(shí)上答案有些讓人驚訝:每一次一個入隊(duì)的block準(zhǔn)備執(zhí)行時,隊(duì)列會將這個block重新放入目標(biāo)隊(duì)列來實(shí)際執(zhí)行這個block合住。
但是等一下绰精,我們不是已經(jīng)假設(shè)了block是在其所在的隊(duì)列上執(zhí)行么?難道一切都是假象透葛?
不是的笨使。由于所有新的隊(duì)列都是以默認(rèn)優(yōu)先級的全局并發(fā)隊(duì)列為目標(biāo)隊(duì)列的,所以我們隊(duì)列中任何準(zhǔn)備執(zhí)行的block基本上都會立即執(zhí)行僚害。除非你改變隊(duì)列的目標(biāo)隊(duì)列硫椰,否則block看上去是“運(yùn)行在你的隊(duì)列上”。
你的隊(duì)列繼承了目標(biāo)隊(duì)列的屬性。將隊(duì)列的目標(biāo)隊(duì)列設(shè)置為較高或較低優(yōu)先級的全局并發(fā)隊(duì)列中的一個靶草,將會改變你的隊(duì)列的優(yōu)先級蹄胰。
只有全局并發(fā)隊(duì)列和主隊(duì)列才能執(zhí)行block。所有其他的隊(duì)列都必須以這兩種隊(duì)列中的一種為目標(biāo)隊(duì)列奕翔。
任務(wù)隊(duì)列實(shí)用專場
讓我們來看個例子裕寨。
幾代人以前,我們很多人的祖父母家的電話都被接成到共用電話線路(party lines)派继。共用電話線路是一種協(xié)議宾袜,一個社區(qū)的所有電話都被接到單一回路中,任何一個接起電話的人都能聽到其他線上的人正在說什么互艾。
讓我們假設(shè)有兩組人试和,住在兩所房子里讯泣,連接了共用電話線路:house1Folks和house2Folks纫普。房間1中的人們喜歡給房間2中的人們打電話。問題是好渠,在他們打電話之前昨稼,沒有人檢查是否有人在占線。讓我們看一下:
// Party line!
#import <Foundation/Foundation.h>
void makeCall(dispatch_queue_t queue, NSString *caller, NSArray *callees) {
// Randomly call someone
NSInteger targetIndex = arc4random() % callees.count;
NSString *callee = callees[targetIndex];
NSLog(@"%@ is calling %@...", caller, callee);
sleep(1);
NSLog(@"...%@ is done calling %@.", caller, callee);
// Wait some random time and call again
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (arc4random() % 1000) * NSEC_PER_MSEC), queue, ^{
makeCall(queue, caller, callees);
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *house1Folks = @[@"Joe", @"Jack", @"Jill"];
NSArray *house2Folks = @[@"Irma", @"Irene", @"Ian"];
dispatch_queue_t house1Queue = dispatch_queue_create("house 1", DISPATCH_QUEUE_CONCURRENT);
for (NSString *caller in house1Folks) {
dispatch_async(house1Queue, ^{
makeCall(house1Queue, caller, house2Folks);
});
}
}
dispatch_main();
return 0;
}
運(yùn)行這段程序拳锚,看看發(fā)生了什么:
Jack is calling Ian...
...Jack is done calling Ian.
Jill is calling Ian...
Joe is calling Ian...
...Jill is done calling Ian.
...Joe is done calling Ian.
Jack is calling Irene...
...Jack is done calling Irene.
Jill is calling Irma...
Joe is calling Ian...
真是簡直了假栓。。霍掺。沒有等上一次通話結(jié)束匾荆,新的通話就被立刻連接了。讓我們看看是否能解決這個問題杆烁。創(chuàng)建一個串行隊(duì)列牙丽,并將它設(shè)置為house1Queue的目標(biāo)隊(duì)列。
// ...
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *house1Folks = @[@"Joe", @"Jack", @"Jill"];
NSArray *house2Folks = @[@"Irma", @"Irene", @"Ian"];
dispatch_queue_t house1Queue = dispatch_queue_create("house 1", DISPATCH_QUEUE_CONCURRENT);
// Set the target queue
dispatch_queue_t partyLine = dispatch_queue_create("party line", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(house1Queue, partyLine);
for (NSString *caller in house1Folks) {
dispatch_async(house1Queue, ^{
makeCall(house1Queue, caller, house2Folks);
});
}
}
dispatch_main();
return 0;
}
結(jié)果如下:
Joe is calling Ian...
...Joe is done calling Ian.
Jack is calling Irma...
...Jack is done calling Irma.
Jill is calling Irma...
...Jill is done calling Irma.
Joe is calling Irma...
...Joe is done calling Irma.
Jack is calling Irene...
...Jack is done calling Irene.
好多了吧兔魂。
這種方式可能并不會立即展示烤芦,因?yàn)椴l(fā)隊(duì)列實(shí)際上按照FIFO(先進(jìn)先出)的順序執(zhí)行入隊(duì)的block:最先入隊(duì)的block會最先被執(zhí)行。但是并發(fā)隊(duì)列不會等待一個block執(zhí)行完成才開始后續(xù)的block析校,所以后續(xù)入隊(duì)的block是并發(fā)開始的构罗,依此類推。
然而我們了解到智玻,隊(duì)列實(shí)際上并不執(zhí)行它自己的block遂唧,而是將準(zhǔn)備執(zhí)行的block重新放入它的目標(biāo)隊(duì)列中。當(dāng)你將一個串行隊(duì)列設(shè)置為并發(fā)隊(duì)列的目標(biāo)隊(duì)列時吊奢,它將以FIFO的順序?qū)⑵渌械腷lock放入串行隊(duì)列來執(zhí)行盖彭。由于串行隊(duì)列在前一個block結(jié)束運(yùn)行之前并不會執(zhí)行新的block,原來在并發(fā)隊(duì)列中的block將被迫以串行方式運(yùn)行∶冢總的來說滔韵,就是串行的目標(biāo)隊(duì)列能夠序列化并發(fā)隊(duì)列。
house1Queue以partyLine為目標(biāo)隊(duì)列掌实,而partyLine又以默認(rèn)優(yōu)先級的全局并發(fā)隊(duì)列為目標(biāo)隊(duì)列陪蜻。這樣,house1Queue中的block會被重新放入partyLine隊(duì)列中贱鼻,然后放入全局并發(fā)隊(duì)列宴卖,最終在全局并發(fā)隊(duì)列中執(zhí)行。
理論上邻悬,創(chuàng)建一個死循環(huán)的目標(biāo)隊(duì)列是可行的症昏,在設(shè)置一序列的目標(biāo)隊(duì)列后又可能重新指向原始隊(duì)列。這么做的后果是不確定的父丰,所以不要這樣做肝谭。
一對多的目標(biāo)隊(duì)列
多個隊(duì)列能指向相同的目標(biāo)隊(duì)列。在房間2中的人們也希望打電話給房間1中的人蛾扇,為他們創(chuàng)建一個隊(duì)列攘烛,將partyLine作為此隊(duì)列的目標(biāo)隊(duì)列。
// Party line
#import <Foundation/Foundation.h>
void makeCall(dispatch_queue_t queue, NSString *caller, NSArray *callees) {
// Randomly call someone
NSInteger targetIndex = arc4random() % callees.count;
NSString *callee = callees[targetIndex];
NSLog(@"%@ is calling %@...", caller, callee);
sleep(1);
NSLog(@"...%@ is done calling %@.", caller, callee);
// Wait some random time and call again
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (arc4random() % 1000) * NSEC_PER_MSEC), queue, ^{
makeCall(queue, caller, callees);
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *house1Folks = @[@"Joe", @"Jack", @"Jill"];
NSArray *house2Folks = @[@"Irma", @"Irene", @"Ian"];
dispatch_queue_t house1Queue = dispatch_queue_create("house 1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t house2Queue = dispatch_queue_create("house 2", DISPATCH_QUEUE_CONCURRENT);
// Set the target queue for BOTH house queues
dispatch_queue_t partyLine = dispatch_queue_create("party line", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(house1Queue, partyLine);
dispatch_set_target_queue(house2Queue, partyLine);
for (NSString *caller in house1Folks) {
dispatch_async(house1Queue, ^{
makeCall(house1Queue, caller, house2Folks);
});
}
for (NSString *caller in house2Folks) {
dispatch_async(house2Queue, ^{
makeCall(house2Queue, caller, house1Folks);
});
}
}
dispatch_main();
return 0;
}
運(yùn)行這段程序镀首,發(fā)現(xiàn)了什么坟漱?
由于這兩個并發(fā)隊(duì)列指向了同一個串行隊(duì)列,兩個并發(fā)隊(duì)列中的block必須一個接一個的執(zhí)行更哄。單個隊(duì)列將兩個并發(fā)隊(duì)列的block序列化了芋齿。
移除一個或兩個并發(fā)隊(duì)列對目標(biāo)隊(duì)列指向,看看發(fā)生了什么成翩。實(shí)際跟你的預(yù)期一致么觅捆?
目標(biāo)隊(duì)列的實(shí)際應(yīng)用
目標(biāo)隊(duì)列能應(yīng)用于一些優(yōu)雅的設(shè)計(jì)模式中。在上面的例子中捕传,我們創(chuàng)建了一個或多個并發(fā)隊(duì)列惠拭,并序列化了他們的運(yùn)行。指定一個串行隊(duì)列作為目標(biāo)隊(duì)列庸论,其實(shí)核心思想就是說职辅,不管有多少獨(dú)立的線程在競爭資源,同一時刻我們只做一件事聂示。這個“一件事”可以是數(shù)據(jù)庫請求域携,訪問物理磁盤驅(qū)動,或者操作一些硬件資源鱼喉。
如果程序中有blocks必須并發(fā)執(zhí)行時秀鞭,設(shè)置一個并發(fā)隊(duì)列指向一個串行隊(duì)列趋观,可能會引發(fā)死鎖。所以謹(jǐn)慎使用此模式锋边。
當(dāng)你需要協(xié)調(diào)不同來源的異步事件時皱坛,例如定時器,網(wǎng)絡(luò)事件豆巨,文件系統(tǒng)等等剩辟,串行目標(biāo)隊(duì)列就變得很重要了。當(dāng)你需要協(xié)調(diào)不同框架往扔,不同對象的事件時贩猎,或者你不能更改類的源代碼時,串行任務(wù)隊(duì)列也非常有用萍膛。我會在后續(xù)的文章中介紹定時器和其他事件源吭服。
正如我的同事Mike E指出的那樣:將并發(fā)隊(duì)列指向串行隊(duì)列,沒有現(xiàn)實(shí)的應(yīng)用意義蝗罗。我表示同意:我很難找到一個列子艇棕,說將一個串行隊(duì)列設(shè)置為一個并行隊(duì)列的目標(biāo)隊(duì)列這種做法優(yōu)于直接dispatch_async到一個串行隊(duì)列上。
并發(fā)任務(wù)隊(duì)列賦予了你不同的魔力:block都能按照它們固有的方式完美執(zhí)行绿饵,直到一個barrier block入隊(duì)欠肾。如果一旦入隊(duì)了一個barrier block瓶颠,會阻止所有入隊(duì)的未執(zhí)行的block執(zhí)行拟赊,直到所有當(dāng)前正在運(yùn)行的block執(zhí)行完畢,以及barrier block完全執(zhí)行完畢粹淋。這就很像在幾條業(yè)務(wù)流中點(diǎn)擊了控制暫停的按鈕吸祟,于是你就能在恢復(fù)執(zhí)行之前做些其他事情。
最后
這里結(jié)束我們對目標(biāo)隊(duì)列的探索桃移。我知道如果你是剛開始接觸GCD屋匕,可能需要多花些精力。實(shí)際上借杰,即使不了解目標(biāo)隊(duì)列过吻,你也能做很多事情。但是有一天蔗衡,你會發(fā)現(xiàn)纤虽,你能夠很優(yōu)雅的通過目標(biāo)隊(duì)列來解決一些至關(guān)重要的開發(fā)問題,而為此學(xué)習(xí)目標(biāo)隊(duì)列的知識也是值得的绞惦。
希望這篇文章是讓人愉快的旅途逼纸。下期我會講述與GCD完美配合的類模式設(shè)計(jì),下期見济蝉。