GCD日記

更新文章:
死鎖 一般都是由于產(chǎn)生了資源競爭瓶盛,而 GCD 死鎖 的充分條件是:

在串行隊列的任務(wù)執(zhí)行過程中,再次向它派發(fā)同步任務(wù)。

當(dāng)我們同步的提交一個任務(wù)時朦蕴,首先會阻塞當(dāng)前隊列指厌,然后等到下一次 runloop 時再在合適的線程中執(zhí)行 block刊愚。

下面的代碼:

dispatch_sync(dispatch_get_main_queue(), ^{});

主隊列環(huán)境下等同于:

//這里是sync或async都不影響結(jié)果
dispatch_sync(dispatch_get_main_queue(), ^{//任務(wù)1
    //任務(wù)1的前半部分
    dispatch_sync(dispatch_get_main_queue(), ^{//任務(wù)2
    });
    //任務(wù)1的下半部分
});

即串行隊列的任務(wù)1執(zhí)行過程中,向它派發(fā)了一個同步任務(wù)2踩验,導(dǎo)致兩個任務(wù)產(chǎn)生競爭鸥诽。

隊列線程 之間并沒有 擁有關(guān)系(ownership);
主隊列環(huán)境=>主線程,主線程環(huán)境≠>主隊列

一箕憾、基礎(chǔ)概念

GCD

GCD 是 libdispatch 的市場名稱牡借,而 libdispatch 作為 Apple 的一個庫,為并發(fā)代碼在多核硬件(跑 iOS 或 OS X )上執(zhí)行提供有力支持袭异。它具有以下優(yōu)點:
1.GCD 能通過推遲昂貴計算任務(wù)并在后臺運行它們來改善你的應(yīng)用的響應(yīng)性能钠龙。
2.GCD 提供一個易于使用的并發(fā)模型而不僅僅只是鎖和線程,以幫助我們避開并發(fā)陷阱御铃。
3.GCD 具有在常見模式(例如單例)上用更高性能的原語優(yōu)化你的代碼的潛在能力碴里。

Serial vs. Concurrent 串行 vs. 并發(fā)
//串行
DISPATCH_QUEUE_SERIAL
//并發(fā)
DISPATCH_QUEUE_CONCURRENT
Synchronous vs. Asynchronous 同步 vs. 異步
//同步執(zhí)行
dispatch_sync(..., ^(block))
//異步執(zhí)行
dispatch_async(..., ^(block))
Critical Section 臨界區(qū)

就是一段代碼不能被并發(fā)執(zhí)行,也就是上真,兩個線程不能同時執(zhí)行這段代碼咬腋。這很常見,因為代碼去操作一個共享資源谷羞,例如一個變量若能被并發(fā)進(jìn)程訪問帝火,那么它很可能會變質(zhì)(譯者注:它的值不再可信)。

Race Condition 競態(tài)條件

這種狀況是指基于特定序列或時機(jī)的事件的軟件系統(tǒng)以不受控制的方式運行的行為湃缎,例如程序的并發(fā)任務(wù)執(zhí)行的確切順序犀填。競態(tài)條件可導(dǎo)致無法預(yù)測的行為,而不能通過代碼檢查立即發(fā)現(xiàn)嗓违。

Deadlock 死鎖

兩個(有時更多)東西——在大多數(shù)情況下九巡,是線程——所謂的死鎖是指它們都卡住了,并等待對方完成或執(zhí)行其它操作蹂季。第一個不能完成是因為它在等待第二個的完成冕广。但第二個也不能完成,因為它在等待第一個的完成偿洁。

Thread Safe 線程安全

線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用撒汉,而不會導(dǎo)致任何問題(數(shù)據(jù)損壞,崩潰涕滋,等)睬辐。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你可以在同一時間在多個線程中使用它而不會有問題溯饵。另一方面侵俗,NSMutableDictionary 就不是線程安全的,應(yīng)該保證一次只能有一個線程訪問它丰刊。

Context Switch 上下文切換

一個上下文切換指當(dāng)你在單個進(jìn)程里切換執(zhí)行不同的線程時存儲與恢復(fù)執(zhí)行狀態(tài)的過程隘谣。這個過程在編寫多任務(wù)應(yīng)用時很普遍,但會帶來一些額外的開銷啄巧。

Concurrency vs Parallelism 并發(fā)與并行

并發(fā)代碼的不同部分可以“同步”執(zhí)行寻歧。然而,該怎樣發(fā)生或是否發(fā)生都取決于系統(tǒng)棵帽。多核設(shè)備通過并行來同時執(zhí)行多個線程熄求;然而渣玲,為了使單核設(shè)備也能實現(xiàn)這一點逗概,它們必須先運行一個線程,執(zhí)行一個上下文切換忘衍,然后運行另一個線程或進(jìn)程逾苫。這通常發(fā)生地足夠快以致給我們并發(fā)執(zhí)行地錯覺。雖然你可以編寫代碼在 GCD 下并發(fā)執(zhí)行枚钓,但 GCD 會決定有多少并行的需求铅搓。并行要求并發(fā),但并發(fā)并不能保證并行搀捷。更深入的觀點是并發(fā)實際上是關(guān)于構(gòu)造星掰。當(dāng)你在腦海中用 GCD 編寫代碼,你組織你的代碼來暴露能同時運行的多個工作片段嫩舟,以及不能同時運行的那些氢烘。如果你想深入此主題,看看 this excellent talk by Rob Pike 家厌。

二播玖、dispatch各知識點與應(yīng)用場景

先上dispatch頭文件分布圖:


dispatch頭文件
① dispatch block

dispatch_block_t是GCD隊列專用block類型,沒有參數(shù)饭于,沒有返回值蜀踏。

簡單定義無參數(shù)回調(diào)函數(shù):

@property (nonatomic,copy) dispatch_block_t blockAction;

使用方式和一般的無參數(shù)回調(diào)函數(shù)相同

② dispatch IO與dispatch data

文件處理接觸較少,mark:
星光社的戴銘--細(xì)說GCD(Grand Central Dispatch)如何用

③ dispatch queue

GCD 提供有 dispatch queues 來處理代碼塊掰吕,這些隊列管理你提供給 GCD 的任務(wù)并用 FIFO 順序執(zhí)行這些任務(wù)果覆。這就保證了第一個被添加到隊列里的任務(wù)會是隊列中第一個開始的任務(wù),而第二個被添加的任務(wù)將第二個開始殖熟,如此直到隊列的終點局待。

所有的調(diào)度隊列(dispatch queues)自身都是線程安全的,你能從多個線程并行的訪問它們。 GCD 的優(yōu)點是顯而易見的燎猛,即當(dāng)你了解了調(diào)度隊列如何為你自己代碼的不同部分提供線程安全恋捆。關(guān)于這一點的關(guān)鍵是選擇正確類型的調(diào)度隊列和正確的調(diào)度函數(shù)來提交你的工作。

Serial Queues 串行隊列 與 Concurrent Queues 并發(fā)隊列
//串行隊列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行隊列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
Queue Types 隊列類型
  • dispatch_get_main_queue
    首先重绷,系統(tǒng)提供給你一個叫做主隊列的特殊隊列沸停。和其它串行隊列一樣,這個隊列中的任務(wù)一次只能執(zhí)行一個昭卓。然而愤钾,它能保證所有的任務(wù)都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程候醒。這個隊列就是用于發(fā)生消息給 UIView 或發(fā)送通知的能颁。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • dispatch_get_global_queue
    系統(tǒng)同時提供給你好幾個并發(fā)隊列。它們叫做全局調(diào)度隊列 倒淫。目前的四個全局隊列有著不同的優(yōu)先級:** DISPATCH_QUEUE_PRIORITY_BACKGROUND伙菊、 DISPATCH_QUEUE_PRIORITY_LOW DISPATCH_QUEUE_PRIORITY_DEFAULT** 以及 ** DISPATCH_QUEUE_PRIORITY_HIGH**敌土。要知道镜硕,Apple 的 API 也會使用這些隊列,所以你添加的任何任務(wù)都不會是這些隊列中唯一的任務(wù)返干。
// DISPATCH_QUEUE_PRIORITY_HIGH(QOS_CLASS_USER_INTERACTIVE)
// DISPATCH_QUEUE_PRIORITY_DEFAULT(QOS_CLASS_USER_INITIATED)
// DISPATCH_QUEUE_PRIORITY_LOW(QOS_CLASS_UTILITY)
// DISPATCH_QUEUE_PRIORITY_BACKGROUND(QOS_CLASS_BACKGROUND)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)

QOS_CLASS_USER_INTERACTIVE:user interactive等級表示任務(wù)需要被立即執(zhí)行提供好的體驗兴枯,用來更新UI,響應(yīng)事件等矩欠。這個等級最好保持小規(guī)模财剖。
QOS_CLASS_USER_INITIATED:user initiated等級表示任務(wù)由UI發(fā)起異步執(zhí)行。適用場景是需要及時結(jié)果同時又可以繼續(xù)交互的時候癌淮。
QOS_CLASS_UTILITY:utility等級表示需要長時間運行的任務(wù)躺坟,伴有用戶可見進(jìn)度指示器。經(jīng)常會用來做計算该默,I/O瞳氓,網(wǎng)絡(luò),持續(xù)的數(shù)據(jù)填充等任務(wù)栓袖。這個任務(wù)節(jié)能匣摘。
QOS_CLASS_BACKGROUND:background等級表示用戶不會察覺的任務(wù),使用它來處理預(yù)加載裹刮,或者不需要用戶交互和對時間不敏感的任務(wù)音榜。

  • 自定義隊列
    最后,你也可以創(chuàng)建自己的串行隊列或并發(fā)隊列捧弃。
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL);

也可以設(shè)置自定義隊列的優(yōu)先級:

//dipatch_queue_attr_make_with_qos_class IOS8.0之后開放
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", attr);
//更多時候使用dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL); //需要設(shè)置優(yōu)先級的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //參考優(yōu)先級
dispatch_set_target_queue(queue, referQueue); //設(shè)置queue的優(yōu)先級和referQueue的一樣

dispatch_set_target_queue也可以設(shè)置隊列層級體系赠叼,設(shè)置dispatch_set_target_queue(queue, referQueue); 也相當(dāng)于把queue的任務(wù)對象指派到referQueue中執(zhí)行擦囊。比如說將多個串行的queue指定到了同一目標(biāo),那么著多個串行queue在目標(biāo)queue上就是同步執(zhí)行的嘴办,不再是并行執(zhí)行瞬场。這部分知識以后用到再進(jìn)行擴(kuò)展

dispatch_after
//延遲3秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // do you something
    });

dispatch_after 工作起來就像一個延遲版的 dispatch_async 。你不能控制實際的執(zhí)行時間涧郊,且一旦 dispatch_after 返回也就不能再取消它贯被。
dispatch_after最佳使用環(huán)境:主隊列(串行)

dispatch_barrier

通過下面這個圖了解一下dispatch_barrier障礙函數(shù)



注意到正常部分的操作就如同一個正常的并發(fā)隊列妆艘。但當(dāng)障礙函數(shù)執(zhí)行時彤灶,它是唯一在執(zhí)行的事件。在障礙完成后批旺,隊列回到一個正常并發(fā)隊列的樣子幌陕。

    //這個例子中3和4會等待1和2執(zhí)行完成后再執(zhí)行
    dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"-------------------1");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"I am a barrier");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------3");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------4");
    });

dispatch_barrier有dispatch_barrier_async和dispatch_barrier_sync兩種,用法與dispatch_async和dispatch_sync一樣汽煮,顧名思義sync會等待block的內(nèi)容執(zhí)行完再繼續(xù)往下執(zhí)行搏熄,而async則不會等待。
dispatch_after最佳使用環(huán)境:自定義隊列(并發(fā))逗物。

dispatch_apply

一句話闡述:并發(fā)地發(fā)起for循環(huán)的迭代搬卒,提高循環(huán)運行效率。
不過這個函數(shù)本身是同步的翎卓,會等待結(jié)構(gòu)體里的內(nèi)容執(zhí)行完再返回。

    dispatch_queue_t concurrentQueue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, concurrentQueue, ^(size_t i) {
        NSLog(@"%zu",i);
    });
    NSLog(@"The end"); //這里有個需要注意的是摆寄,dispatch_apply這個是會阻塞主線程的失暴。這個log打印會在dispatch_apply都結(jié)束后才開始執(zhí)行

dispatch_after最佳使用環(huán)境:并發(fā)隊列

④ dispatch groups

即調(diào)度組微饥《喊牵可以對任務(wù)進(jìn)行監(jiān)聽,這些任務(wù)可以是同步的欠橘,也可以是異步的矩肩,即便在不同的隊列也行。而且在整個組的任務(wù)都完成時肃续,Dispatch Group 可以用同步的或者異步的方式通知你黍檩。如果要監(jiān)控的任務(wù)在不同隊列,那就用一個 dispatch_group_t 的實例來記下這些不同的任務(wù)始锚。
以下代碼介紹dispatch_group_t的創(chuàng)建與執(zhí)行方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建dispatch_group_t方法
dispatch_group_t downloadGroup = dispatch_group_create();
//代表group一個任務(wù)執(zhí)行過程
dispatch_group_enter(downloadGroup);
NSLog(@"-------------------");
dispatch_group_leave(downloadGroup);
//等價于dispatch_group_enter+dispatch_group_leave
dispatch_group_async(group, queue, ^{
    //do something
    NSLog(@"-------------------");
});

當(dāng)組中所有的事件都完成時刽酱,GCD 的 API 以下提供了兩種通知方式颜凯。

  • dispatch_group_wait
//異步調(diào)用全局調(diào)度隊列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1 
      //此時group在全局調(diào)度隊列中
      dispatch_group_t downloadGroup = dispatch_group_create(); // 2 
 
        for (NSInteger i = 0; i < 3; i++) {
            dispatch_group_enter(downloadGroup); // 3 
            self.block = ^(){
                NSLog(@"-------------------");
                dispatch_group_leave(downloadGroup); // 4 
            };
        } 
        dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5 
        dispatch_async(dispatch_get_main_queue(), ^{ // 6 
          NSLog(@"-------------------");
        }); 
    }); 
  • dispatch_group_notify
//此時group在主隊列(串行)中
  dispatch_group_t downloadGroup = dispatch_group_create(); // 1 

  for (NSInteger i = 0; i < 3; i++) {
      dispatch_group_enter(downloadGroup); // 2 
      self.block = ^(){
          NSLog(@"-------------------");
          dispatch_group_leave(downloadGroup); // 3 
      };
  }  
  dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4 
      NSLog(@"-------------------");
  }); 

需要注意的是dispatch_group_wait會阻塞當(dāng)前線程夸赫,而dispatch_group_notify不會。

關(guān)于何時以及怎樣使用有著不同的隊列類型的 Dispatch Group :

  1. 自定義串行隊列:它很適合當(dāng)一組任務(wù)完成時發(fā)出通知禾进。
  2. 主隊列(串行):它也很適合這樣的情況。但如果你要同步地等待所有工作地完成殿怜,那你就不應(yīng)該使用它典蝌,因為你不能阻塞主線程。然而头谜,如果需要在幾個較長任務(wù)(例如網(wǎng)絡(luò)調(diào)用)完成后更新 UI 的話赠法,dispatch_group_notify是非常適合的方式。
  3. 并發(fā)隊列:它也很適合 Dispatch Group 和完成時通知乔夯。
⑤dispatch once

dispatch_once_t要是全局或static變量砖织,保證dispatch_once_t只有一份實例

+ (UIColor *)boringColor;
{
     static UIColor *color;
     //只運行一次
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
     });
     return color;
}
⑥dispatch semaphore

信號量是用來保證關(guān)鍵代碼段不被所設(shè)定次數(shù)以上并發(fā)調(diào)用的手段。在進(jìn)入一個關(guān)鍵代碼段之前末荐,線程必須獲取一個信號量侧纯,同時最多只能有設(shè)定次數(shù)個線程可以訪問臨界區(qū)。其他想使用資源的線程必須在一個FIFO隊列里等待甲脏。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    NSMutableArray *array = [NSMutableArrayarray];

    for (int index = 0; index < 100000; index++) {

        dispatch_async(queue, ^(){

            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//

            NSLog(@"addd :%d", index);

            [array addObject:[NSNumber numberWithInt:index]];

            dispatch_semaphore_signal(semaphore);

        });

    }

在執(zhí)行到wait方法的時候如果semaphore計數(shù)大于等于1.計數(shù)-1眶熬,返回,程序繼續(xù)運行块请。如果計數(shù)為0娜氏,則等待。這里設(shè)置的等待時間是一直等待墩新。dispatch_semaphore_signal(semaphore);計數(shù)+1.在這兩句代碼中間的執(zhí)行代碼贸弥,每次只會允許一個線程進(jìn)入,這樣就有效的保證了在多線程環(huán)境下海渊,只能有一個線程進(jìn)入绵疲。

參考鏈接

星光社的戴銘--細(xì)說GCD(Grand Central Dispatch)如何用
GCD 深入理解(二)
夏天然后--帶你系統(tǒng)學(xué)習(xí)GCD

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市臣疑,隨后出現(xiàn)的幾起案子盔憨,更是在濱河造成了極大的恐慌,老刑警劉巖讯沈,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郁岩,死亡現(xiàn)場離奇詭異,居然都是意外死亡缺狠,警方通過查閱死者的電腦和手機(jī)问慎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儒老,“玉大人蝴乔,你說我怎么就攤上這事⊥苑” “怎么了薇正?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵片酝,是天一觀的道長。 經(jīng)常有香客問我挖腰,道長雕沿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任猴仑,我火速辦了婚禮审轮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辽俗。我一直安慰自己疾渣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布崖飘。 她就那樣靜靜地躺著榴捡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱浴。 梳的紋絲不亂的頭發(fā)上吊圾,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音翰蠢,去河邊找鬼项乒。 笑死,一個胖子當(dāng)著我的面吹牛梁沧,可吹牛的內(nèi)容都是我干的檀何。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼趁尼,長吁一口氣:“原來是場噩夢啊……” “哼埃碱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酥泞,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啃憎,沒想到半個月后芝囤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辛萍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年悯姊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贩毕。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡悯许,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辉阶,到底是詐尸還是另有隱情先壕,我是刑警寧澤瘩扼,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站垃僚,受9級特大地震影響集绰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谆棺,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一栽燕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧改淑,春花似錦碍岔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侍郭,卻和暖如春询吴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亮元。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工猛计, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爆捞。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓奉瘤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親煮甥。 傳聞我的和親對象是個殘疾皇子盗温,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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