【翻譯】GCD Concurrent Queues

本篇文章由我們團(tuán)隊(duì)的郭杰童鞋翻譯完成。這是關(guān)于GCD系列的第三篇文章覆积,原文是GCD Concurrent Queues听皿。


如果說串行隊(duì)列是互斥量更好的替代品的話,那么并發(fā)隊(duì)列就是線程的一個(gè)更好的替代品宽档。

并發(fā)隊(duì)列可以讓你入隊(duì)的block且并發(fā)執(zhí)行尉姨,而不需要等待前面入隊(duì)的?block完成運(yùn)行。

多次運(yùn)行以下程序:

#import <Foundation/Foundation.h>

void print(int number) {
    for (int count = 0; count < 10; ++count) {
        NSLog(@"%d", number);
    }
}

int main(int argc, const char * argv[]) {
    dispatch_queue_t queue = dispatch_queue_create("My concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    
    @autoreleasepool {
        for (int index = 0; index < 5; ++index) {
            dispatch_async(queue, ^{
                print(index);
            });
        }
    }
    dispatch_main();
    return 0;
}

dispatch_async()告訴GCD來入隊(duì)block塊吗冤,而不是等到block塊移動(dòng)之前完成又厉。這就使我們能夠快速的將5個(gè)block置于我們剛剛創(chuàng)建的并發(fā)隊(duì)列中九府。

當(dāng)?shù)谝粋€(gè)block塊入隊(duì)時(shí),隊(duì)列是空的覆致,因此如果當(dāng)前隊(duì)列是串行侄旬,它會(huì)按照同樣的方式來運(yùn)行。但是煌妈,當(dāng)?shù)诙€(gè)block被入隊(duì)時(shí)勾怒,即使第一個(gè)block還沒有運(yùn)行完成,第二個(gè)也依然會(huì)運(yùn)行声旺。當(dāng)然了,這種方式也同樣適用于第三個(gè)段只、第四和第五個(gè)block腮猖,它們都會(huì)在同一時(shí)間開始運(yùn)行。

隊(duì)列上的每一個(gè)block在創(chuàng)建時(shí)會(huì)捕獲index索引值赞枕, 并會(huì)打印10次記錄澈缺。這個(gè)程序的輸出符合您的預(yù)期嗎?為什么每次運(yùn)行程序的輸出結(jié)果都是不一樣的呢?

<p>

如果我們使用串行隊(duì)列的話炕婶,程序的輸出會(huì)有什么不同呢姐赡?嘗試將DISPATCH_QUEUE_CONCURRENT修改為DISPATCH_QUEUE_SERIAL,然后再次運(yùn)行程序柠掂,試試看项滑。

用隊(duì)列,不用線程

你可能已經(jīng)錯(cuò)過了線程涯贞,但是上面的程序在不使用pthread_create()NSThread的情況下枪狂,毫不費(fèi)力地創(chuàng)建并執(zhí)行五個(gè)線程。由于在并發(fā)隊(duì)列中每一個(gè)block都必須是同時(shí)運(yùn)行的宋渔,GCD會(huì)自動(dòng)創(chuàng)建(或征用)一個(gè)線程來運(yùn)行它們中的每一個(gè)州疾。每個(gè)block一旦完成,該線程會(huì)被摧毀或者返回到一個(gè)線程池中皇拣。使用GCD严蓖,你可以專注于隊(duì)列,并讓有關(guān)線程庫去考慮線程問題氧急。

雖然你不用手動(dòng)管理線程颗胡,但這并不意味著你可以忽視線程的限制。如果入隊(duì)的并發(fā)block比可用的線程更多吩坝,你的程序可能會(huì)出問題杭措。

障礙(Barriers)

在這個(gè)點(diǎn)上的一個(gè)很自然的問題是:如果并發(fā)隊(duì)列允許所有的block執(zhí)行,那么為什么它們被稱為”隊(duì)列“呢钾恢?它不是更像一個(gè)可以加入并發(fā)執(zhí)行block的堆嗎手素?

當(dāng)你考慮障礙時(shí)鸳址,并發(fā)隊(duì)列的行為看起來就像隊(duì)列了。?使用dispatch_barrier_sync()或者dispatch_barrier_async()入隊(duì)的block會(huì)帶來一些有意思的事情:這個(gè)block塊將會(huì)被入隊(duì)泉懦,但是會(huì)等到所有的之前入隊(duì)的block執(zhí)行完成后才開始執(zhí)行稿黍。除此之外,在?barrier block后面入隊(duì)的所有的block崩哩,會(huì)等到到barrier block本身已經(jīng)執(zhí)行完成之后才繼續(xù)執(zhí)行巡球。barrier block通常被看作是一系列的并發(fā)操作集合中的"choke points"(咽喉要道)。

為了展示障礙block邓嘹,看看下面的程序:

#import <Foundation/Foundation.h>

void print(int number) {
    for (int count = 0; count < 10; ++count) {
        NSLog(@"%d", number);
    }
}

int main(int argc, const char * argv[]) {
    dispatch_queue_t queue = dispatch_queue_create("My concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_suspend(queue); // Suspend the queue so blocks are enqueued, but not executed
    
    @autoreleasepool {
        // Enqueue five blocks
        for (int index = 0; index < 5; ++index) {
            dispatch_async(queue, ^{
                print(index);
            });
        }
        
        // Enqueue a barrier
        dispatch_barrier_async(queue, ^{
            NSLog(@"--- This is a barrier ---");
        });
        
        // Enqueue five more blocks
        for (int index = 5; index < 10; ++index) {
            dispatch_async(queue, ^{
                print(index);
            });
        }
    }
    dispatch_resume(queue); // Go!
    dispatch_main();
    return 0;
}

運(yùn)行這個(gè)程序酣栈。可以注意到barrier之前的那些block汹押,只有索引為0到4的block是被允許執(zhí)行到完成矿筝,而在這個(gè)barrier之后,僅僅序號(hào)為5到9的block會(huì)被執(zhí)行棚贾。然而窖维,在?barrier兩邊,每組5個(gè)block塊被允許在同一個(gè)時(shí)間執(zhí)行妙痹。

讀寫鎖

在我上一篇博客中铸史,我講了如何使用串行隊(duì)列去?保護(hù)一組狀態(tài)變量。使用這個(gè)技術(shù)怯伊,僅有一個(gè)線程可以在一個(gè)時(shí)間內(nèi)訪問一個(gè)變量琳轿,從而保證了原子行為。

但是說實(shí)話耿芹,我們沒有必要在讀取數(shù)據(jù)時(shí)去保護(hù)這些數(shù)據(jù):我們僅僅需要在異步修改時(shí)去保護(hù)它們利赋。允許多個(gè)線程讀取數(shù)據(jù)而不改變數(shù)據(jù)從某些角度(如性能)來說是非常好的。

我們需要的是一個(gè)讀寫鎖猩系,即寫入的時(shí)候串行化訪問操作媚送,但是允許多個(gè)讀操作并發(fā)。

我們可以使用異步隊(duì)列和barrier輕松實(shí)現(xiàn)一個(gè)讀寫鎖寇甸。如下所示:

#import <Foundation/Foundation.h>

dispatch_queue_t queue;

NSString *he = @"Luke";
NSString *she = @"Megan";

void printAndRepeat() {
    NSLog(@"%@ likes %@!", he, she);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue,
                   // This block is dispatch_async'd to the concurrent queue after 1 second
                   ^{
                       printAndRepeat();
                   });
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        queue = dispatch_queue_create("Reader-writer queue", DISPATCH_QUEUE_CONCURRENT);
        
        // Create readers
        for (int index = 0; index < 5; ++index) {
            dispatch_async(queue, ^{
                printAndRepeat();
            });
        }
        
        // Change the variables after 5 seconds
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(),
                       // This block is enqueued onto the main queue after 5 seconds.
                       ^{
                           dispatch_barrier_async(queue, ^{
                               he = @"Don";
                               she = @"Alice";
                           });
                       });
    }
    dispatch_main();
    return 0;
}

你現(xiàn)在可以忽略調(diào)用dispatch_after()塘偎,它只是簡(jiǎn)單的告訴GCD在一段時(shí)間后入隊(duì)一個(gè)block。

在這個(gè)例子當(dāng)中拿霉,barrier block是保證了修改操作的原子性吟秩。因?yàn)閎arrier block總是不間斷的運(yùn)行,你將不會(huì)看到有“Luke like Alice!”打印出來绽淘。

恭喜你涵防!你已經(jīng)了解了關(guān)于并發(fā)隊(duì)列的所有內(nèi)容,以及它們是怎樣被用來代替線程的并創(chuàng)建有效的讀寫鎖沪铭。在下一篇文章中壮池,我們將解析全局并發(fā)隊(duì)列和目標(biāo)隊(duì)列偏瓤。下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椰憋,一起剝皮案震驚了整個(gè)濱河市厅克,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橙依,老刑警劉巖证舟,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異窗骑,居然都是意外死亡女责,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門创译,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抵知,“玉大人,你說我怎么就攤上這事昔榴。” “怎么了碘橘?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵互订,是天一觀的道長。 經(jīng)常有香客問我痘拆,道長仰禽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任纺蛆,我火速辦了婚禮吐葵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桥氏。我一直安慰自己温峭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布字支。 她就那樣靜靜地躺著凤藏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堕伪。 梳的紋絲不亂的頭發(fā)上揖庄,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音欠雌,去河邊找鬼蹄梢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛富俄,可吹牛的內(nèi)容都是我干的禁炒。 我是一名探鬼主播而咆,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼齐苛!你這毒婦竟也來了翘盖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤凹蜂,失蹤者是張志新(化名)和其女友劉穎馍驯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玛痊,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汰瘫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了擂煞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混弥。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖对省,靈堂內(nèi)的尸體忽然破棺而出蝗拿,到底是詐尸還是另有隱情,我是刑警寧澤蒿涎,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布哀托,位于F島的核電站,受9級(jí)特大地震影響劳秋,放射性物質(zhì)發(fā)生泄漏仓手。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一玻淑、第九天 我趴在偏房一處隱蔽的房頂上張望嗽冒。 院中可真熱鬧,春花似錦补履、人聲如沸添坊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帅腌。三九已至,卻和暖如春麻汰,著一層夾襖步出監(jiān)牢的瞬間速客,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工五鲫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溺职,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像浪耘,于是被迫代替她去往敵國和親乱灵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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