【翻譯】GCD Target Queues

原文發(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ì),下期見济蝉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杰刽,一起剝皮案震驚了整個濱河市菠发,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贺嫂,老刑警劉巖滓鸠,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異第喳,居然都是意外死亡哥力,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門墩弯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吩跋,“玉大人,你說我怎么就攤上這事渔工⌒颗ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵引矩,是天一觀的道長梁丘。 經(jīng)常有香客問我,道長旺韭,這世上最難降的妖魔是什么氛谜? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮区端,結(jié)果婚禮上值漫,老公的妹妹穿的比我還像新娘。我一直安慰自己织盼,他們只是感情好杨何,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沥邻,像睡著了一般危虱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唐全,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天埃跷,我揣著相機(jī)與錄音,去河邊找鬼邮利。 笑死弥雹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的近弟。 我是一名探鬼主播缅糟,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼祷愉!你這毒婦竟也來了窗宦?” 一聲冷哼從身側(cè)響起赦颇,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赴涵,沒想到半個月后媒怯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡髓窜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年扇苞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寄纵。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡鳖敷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出程拭,到底是詐尸還是另有隱情定踱,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布恃鞋,位于F島的核電站崖媚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恤浪。R本人自食惡果不足惜畅哑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望水由。 院中可真熱鬧荠呐,春花似錦、人聲如沸绷杜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞭盟。三九已至,卻和暖如春瑰剃,著一層夾襖步出監(jiān)牢的瞬間齿诉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工晌姚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粤剧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓挥唠,卻偏偏與公主長得像抵恋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宝磨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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