[譯] GCD 系列(四)之—目標(biāo)隊(duì)列

本文翻譯自GCD Target Queues,個(gè)人覺(jué)得寫(xiě)得很不錯(cuò)饲帅,準(zhǔn)備翻譯一遍复凳,查了一下卻發(fā)現(xiàn)已經(jīng)有前輩翻譯過(guò)了瘤泪,不過(guò)還是自己自動(dòng)翻譯了一遍,在不改變?cè)獾那疤嵯掠耍隽艘恍┬薷亩酝荆尤胍恍┳约旱睦斫狻7g有參考這篇 GCD目標(biāo)隊(duì)列(GCD Target Queues)(侵刪)髓棋。

這是 GCD 系列文章 第四篇实檀。

gcd-logo.png

我們來(lái)看看 GCD 的一個(gè)優(yōu)雅特性:目標(biāo)隊(duì)列。

在開(kāi)始之前學(xué)習(xí)一些列隊(duì)列之前仲锄,我們先來(lái)看看特殊的一種隊(duì)列:全局隊(duì)列劲妙。

全局隊(duì)列(Global concurrent queues)

GCD 提供四種可用的并發(fā)全局隊(duì)列,這些隊(duì)列比較特殊儒喊,因?yàn)樗怯上到y(tǒng)庫(kù)自動(dòng)創(chuàng)建的镣奋,永遠(yuǎn)不會(huì)被阻塞。即使遇到障礙(barrier blocks) 任務(wù)怀愧,其他任務(wù)也不會(huì)被阻塞侨颈。這些隊(duì)列都是并發(fā)的,所有入隊(duì)列的任務(wù)都會(huì)并發(fā)執(zhí)行芯义。

四種隊(duì)列對(duì)應(yīng)的優(yōu)先級(jí)分別是:

  • DISPATCH_QUEUE_PRIORITY_HIGH
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND

高優(yōu)先級(jí)隊(duì)列里的任務(wù)比低優(yōu)先級(jí)隊(duì)列里的任務(wù)優(yōu)先執(zhí)行哈垢。

GCD 的這些全局隊(duì)列同時(shí)也扮演一個(gè)線(xiàn)程優(yōu)先級(jí)的角色。像線(xiàn)程一樣扛拨,執(zhí)行在高優(yōu)先級(jí)的隊(duì)列中的任務(wù)會(huì)搶占 CPU 資源耘分,使得低優(yōu)先級(jí)的隊(duì)列處于「饑餓」?fàn)顟B(tài),即無(wú)法得到執(zhí)行绑警。

你可以用如下方式獲取全局的并發(fā)隊(duì)列:

dispatch_queue_t defaultPriorityGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

目標(biāo)隊(duì)列(Target Queue)

這些全局隊(duì)列該如何使用呢求泰?說(shuō)出來(lái)可能會(huì)令你驚訝:你已經(jīng)在使用它們了。任何隊(duì)列一旦你創(chuàng)建都會(huì)對(duì)應(yīng)有一個(gè)目標(biāo)隊(duì)列计盒。默認(rèn)情況下渴频,所有隊(duì)列(主隊(duì)列除外)都會(huì)把 DISPATCH_QUEUE_PRIORITY_DEFAULT優(yōu)先級(jí)的全局并發(fā)隊(duì)列作為目標(biāo)隊(duì)列。

隊(duì)列竟然還有目標(biāo)隊(duì)列北启?是不是有點(diǎn)驚訝:實(shí)際上每當(dāng)隊(duì)列中的任務(wù)準(zhǔn)備好要執(zhí)行的時(shí)候卜朗,隊(duì)列會(huì)把該任務(wù)重入(重新放入)到目標(biāo)隊(duì)列,真正的執(zhí)行在目標(biāo)隊(duì)列中咕村。

等等场钉,難道任務(wù)不是在我們所提交的隊(duì)列里面執(zhí)行的?這些全都是謊言懈涛?

不是的惹悄,因?yàn)樗凶约簞?chuàng)建的隊(duì)列(包括串行隊(duì)列)都會(huì)把默認(rèn)優(yōu)先級(jí)的全局并發(fā)隊(duì)列當(dāng)做目標(biāo)隊(duì)列,前面說(shuō)過(guò)全局并發(fā)隊(duì)列不會(huì)被阻塞肩钠,等待工作都是在提交的隊(duì)列中的泣港,一旦輪到執(zhí)行,就會(huì)被提交到目標(biāo)隊(duì)列中价匠,并立刻開(kāi)始執(zhí)行当纱。所以除非是你自定義目標(biāo)隊(duì)列,否則你完全可以抽象的認(rèn)為任務(wù)就是在你提交的隊(duì)列中開(kāi)始執(zhí)行的踩窖。

你創(chuàng)建的隊(duì)列的優(yōu)先級(jí)繼承自目標(biāo)全局隊(duì)列坡氯。把目標(biāo)全局隊(duì)列的優(yōu)先級(jí)設(shè)置的更高或者更低,會(huì)同時(shí)改變你創(chuàng)建隊(duì)列的優(yōu)先級(jí)洋腮。

只有全局隊(duì)列和主隊(duì)列會(huì)執(zhí)行任務(wù)箫柳,它們是所有其他隊(duì)列的目標(biāo)隊(duì)列。

來(lái)看一張來(lái)自 objc 的圖:

gcd-pool.png

該圖說(shuō)明了自定義隊(duì)列分成兩路:一路是串行隊(duì)列進(jìn)入 GCD 的主隊(duì)列啥供,另一路是進(jìn)入 GCD 默認(rèn)優(yōu)先級(jí)的全局并發(fā)隊(duì)列悯恍。最后都在系統(tǒng)維護(hù)的線(xiàn)程池中被執(zhí)行。

玩轉(zhuǎn)目標(biāo)隊(duì)列(Party time with target queues)

先來(lái)看一個(gè)列子伙狐。

很久以前涮毫,我們的祖先使用公用線(xiàn)路打電話(huà)的。一個(gè)社區(qū)里面的電話(huà)都是在一條線(xiàn)路上通信的贷屎,任何人只要拿起電話(huà)罢防,就可以聽(tīng)到其他人打電話(huà)的的聲音。

假設(shè)我們有兩小組的人唉侄,住在兩間房子里面咒吐,通過(guò)單一的線(xiàn)路連接: house1Folks 和 house2Folks。房間一的人喜歡給房間二的人打電話(huà)属划。問(wèn)題是恬叹,他們?cè)诖螂娫?huà)前不回去檢查是否有其他人在打電話(huà)。來(lái)看例子:

// Party line!

#import <Foundation/Foundation.h>

void makeCall(dispatch_queue_t queue, NSString *caller, NSArray *callees) {
    // caller 隨機(jī)打電話(huà)給 callees 里面的一個(gè)人
    NSInteger targetIndex = arc4random() % callees.count;
    NSString *callee = callees[targetIndex];

    NSLog(@"%@ is calling %@...", caller, callee);
    sleep(1);
    NSLog(@"...%@ is done calling %@.", caller, callee);

    // 隨機(jī)等待一段時(shí)間榴嗅,然后接著打
    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);
            });
        }
    }
    return 0;
}

運(yùn)行程序來(lái)看看結(jié)果:

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...

實(shí)在太亂了妄呕!他們都沒(méi)有等前面的的人掛斷電話(huà),就自己開(kāi)始打電話(huà)了嗽测。我們來(lái)看看能不能捋一捋绪励,創(chuàng)建一個(gè)串行隊(duì)列作為 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.

現(xiàn)在好多了唠粥。

這可能不太明顯疏魏,但是并發(fā)隊(duì)列的確以先進(jìn)先出(FIFO)的順序執(zhí)行任務(wù):第一個(gè)入隊(duì)列的任務(wù)就會(huì)被第一個(gè)執(zhí)行。但是并發(fā)隊(duì)列不會(huì)等待前一個(gè)任務(wù)結(jié)束之后才開(kāi)始提交下一個(gè)任務(wù)晤愧,所以下一個(gè)任務(wù)緊接著也提交了大莫,之后的任務(wù)也一樣。

第一次看的時(shí)候可能不太明白官份,其實(shí)隊(duì)列還是并發(fā)的提交到的只厘,只是在它們真正準(zhǔn)備好執(zhí)行的時(shí)候烙丛,必須一個(gè)一個(gè)的放到串行隊(duì)列里面執(zhí)行(譯者理解)。

但是我們知道一個(gè)隊(duì)列里面的任務(wù)實(shí)際上不是在這個(gè)隊(duì)列中執(zhí)行的羔味,而是把準(zhǔn)備好執(zhí)行的任務(wù)重入到它的目標(biāo)隊(duì)列中執(zhí)行河咽。我們把一個(gè)串行隊(duì)列作為并發(fā)隊(duì)列的目標(biāo)隊(duì),它將會(huì)把隊(duì)列以先進(jìn)先出的順序在串行隊(duì)列中執(zhí)行赋元。因?yàn)樵诖嘘?duì)列中執(zhí)行任務(wù)必須等到它前一個(gè)任務(wù)執(zhí)行結(jié)束忘蟹,原來(lái)在并行隊(duì)列中運(yùn)行的任務(wù)并強(qiáng)制串行化。本質(zhì)上說(shuō)搁凸,一個(gè)串行的目標(biāo)隊(duì)列可以串行化一個(gè)并行的隊(duì)列媚值。(英文的特點(diǎn)是可以把內(nèi)容講的很細(xì),所以有的時(shí)候看英文的可能更好理解一些护糖。)

house1Queue 把 partyLine 隊(duì)列作為其目標(biāo)隊(duì)列褥芒,而 partyLine 會(huì)把默認(rèn)優(yōu)先級(jí)全局的并行隊(duì)列作為其目標(biāo)隊(duì)列。所以在 house1Queue 的任務(wù)會(huì)被重入到 partyLine 隊(duì)列椅文,最后被加入全局并行隊(duì)列執(zhí)行喂很。

缺乏對(duì)目標(biāo)隊(duì)列的認(rèn)知,容易造成死循環(huán)的麻煩皆刺,比如不小心設(shè)置了一系列的目標(biāo)隊(duì)列最后可能指向最開(kāi)始的那個(gè)隊(duì)列少辣。最終造成不可預(yù)測(cè)的后果,所以不要這么做羡蛾。

多個(gè)隊(duì)列設(shè)置同一個(gè)目標(biāo)隊(duì)列

多個(gè)隊(duì)列可以設(shè)置同一個(gè)隊(duì)列作為目標(biāo)隊(duì)列漓帅。房間二的人也想打電話(huà)給房間一的人,所以為他們創(chuàng)建一個(gè)并發(fā)隊(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)行程序,你觀(guān)察到了什么浪藻?

因?yàn)閮蓚€(gè)并發(fā)隊(duì)列都是同一個(gè)串行隊(duì)列捐迫,來(lái)自?xún)蓚€(gè)隊(duì)列的任務(wù)必須等待前一個(gè)任務(wù)執(zhí)行完畢。把一個(gè)串行隊(duì)列作為目標(biāo)隊(duì)列可以串行化來(lái)自?xún)蓚€(gè)并行隊(duì)列的任務(wù)爱葵。

移除一個(gè)或者兩個(gè)并行隊(duì)列的目標(biāo)隊(duì)列施戴,看看會(huì)發(fā)生什么?

兩頭開(kāi)始同時(shí)打電話(huà)萌丈,不等待電話(huà)掛斷赞哗,下一個(gè)人有開(kāi)始打電話(huà),會(huì)完全錯(cuò)亂辆雾。

真實(shí)世界的目標(biāo)隊(duì)列

目標(biāo)隊(duì)列可以應(yīng)用在一些優(yōu)雅的設(shè)計(jì)模式中肪笋。上面的例子,我們使用一個(gè)或者多個(gè)并發(fā)隊(duì)列并且使其串行化。使用串行隊(duì)列作為目標(biāo)隊(duì)列表明你在同一時(shí)刻只想做一件事情藤乙,不管有多少獨(dú)立的線(xiàn)程在競(jìng)爭(zhēng)資源猜揪。這「一件事」可以是讀取數(shù)據(jù)庫(kù),操作物理磁盤(pán)驅(qū)動(dòng)湾盒,或者操作一些硬件資源湿右。

設(shè)置并發(fā)隊(duì)列的目標(biāo)隊(duì)列為串行隊(duì)列可能回造成死鎖,如果某個(gè)任務(wù)必須再并發(fā)隊(duì)列中執(zhí)行的話(huà)罚勾。所以小心使用這種模式。

當(dāng)你想要協(xié)調(diào)不同來(lái)源的異步事件時(shí)吭狡,串行目標(biāo)隊(duì)列就顯得非常的重要尖殃。例如:定時(shí)器,網(wǎng)絡(luò)事件划煮,文件系統(tǒng)的活動(dòng)等送丰。當(dāng)你想要協(xié)調(diào)來(lái)自不同框架的對(duì)象,或者不能修改源碼弛秋,這顯得非常的有用器躏。我們會(huì)在后面的文章講到定時(shí)器和事件源。

正如我的同事 Mike E. 所說(shuō)蟹略,將串行隊(duì)列設(shè)置為并發(fā)隊(duì)列的目標(biāo)沒(méi)有真實(shí)的使用場(chǎng)景登失。我表示同意:我們很難找到一個(gè)例子,設(shè)置并發(fā)的目標(biāo)隊(duì)列到串行隊(duì)列上優(yōu)于直接使用 dispach_async 提交到串行隊(duì)列上挖炬。

并發(fā)隊(duì)列給了你不一樣的魔力:你可以讓任務(wù)按照其本來(lái)的方式執(zhí)行揽浙,直到你加入一個(gè)障礙 block。如果你這么做意敛,會(huì)使得所有已經(jīng)入隊(duì)了的任務(wù)被暫停馅巷,直到當(dāng)前已經(jīng)開(kāi)始執(zhí)行的任務(wù)以及障礙任務(wù)執(zhí)行完畢,它們才會(huì)繼續(xù)執(zhí)行草姻。就像按下多條流水下的總暫停開(kāi)關(guān)钓猬,在重新啟動(dòng)開(kāi)關(guān)之前你可以做一些想做的事情。

最后

到這里目標(biāo)隊(duì)列的內(nèi)容就講完了撩独。我想如果你剛開(kāi)始接觸 GCD 的話(huà)敞曹,這些知識(shí)可能會(huì)有點(diǎn)難。事實(shí)上跌榔,你可以繼續(xù)幸敢煅悖快樂(lè)的前行不用管 GCD 的目標(biāo)隊(duì)列。如果有一天你遇到一個(gè)難題僧须,可以用目標(biāo)隊(duì)列來(lái)優(yōu)雅的解決的話(huà)纲刀,你會(huì)認(rèn)為學(xué)習(xí)這些是值得的。

我希望學(xué)習(xí)這些你是快樂(lè)的,下次相見(jiàn)示绊,我們會(huì)講講如何使用 GCD 來(lái)實(shí)現(xiàn)設(shè)計(jì)類(lèi)锭部。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市面褐,隨后出現(xiàn)的幾起案子拌禾,更是在濱河造成了極大的恐慌,老刑警劉巖展哭,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湃窍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡匪傍,警方通過(guò)查閱死者的電腦和手機(jī)您市,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)役衡,“玉大人茵休,你說(shuō)我怎么就攤上這事∈中” “怎么了榕莺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)棵介。 經(jīng)常有香客問(wèn)我钉鸯,道長(zhǎng),這世上最難降的妖魔是什么鞍时? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任亏拉,我火速辦了婚禮,結(jié)果婚禮上逆巍,老公的妹妹穿的比我還像新娘及塘。我一直安慰自己,他們只是感情好锐极,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布笙僚。 她就那樣靜靜地躺著,像睡著了一般灵再。 火紅的嫁衣襯著肌膚如雪肋层。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天翎迁,我揣著相機(jī)與錄音栋猖,去河邊找鬼。 笑死汪榔,一個(gè)胖子當(dāng)著我的面吹牛蒲拉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雌团,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼燃领!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起锦援,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猛蔽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后灵寺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體往枣,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卒煞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蛛淋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹃答。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炕柔。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞧预,死狀恐怖驹止,靈堂內(nèi)的尸體忽然破棺而出放闺,到底是詐尸還是另有隱情胀糜,我是刑警寧澤颅拦,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站教藻,受9級(jí)特大地震影響距帅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜括堤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一碌秸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悄窃,春花似錦讥电、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至横媚,卻和暖如春纠炮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灯蝴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工恢口, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穷躁。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓耕肩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子看疗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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