iOS 多線程執行完網絡任務ABC再執行D

前言
我發現網絡上很多介紹多線程的案例感覺都是錯誤的例子。也不能說錯的,例如用異步并發隊列的時候,他們只是打印了一個log,這種打印的行為本身就是同步任務,肯定按照最簡單的例子進行打印了,看到的效果自然是很多文章所說的。但是如果你Block里面的任務是網絡請求呢?還能保證網絡異步任務的一致性?很顯然大部分文章壓根沒有考慮任務的異步性,而且顯示開發中大部分任務都是異步的,因此本文先從網上的同步任務,也就是比較簡單的案例開始介紹,最后再通過一道阿里的面試題來引出實際開發中的問題,這個題目非常有代表性。

線程同步
同步異步并發串行什么的可以看看這個簡單的介紹

阿里有個面試題

使用GCD如何實現A,B,C三個任務并發,完成后執行任務D?

// 異步 并發隊列 當前線程不等待,而且任務是并發隊列,一次可以執行多個
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 第一種 同步任務
NSLog(@"同步任務打印1");
// 第二種 網絡請求發送任務 (發送這個操作是任務,而網絡返回的報文是不歸任務管理的,因此發送任務發送之后,任務已經結束)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"異步任務返回報文打印1");
});
});
可以看到異步并發任務有兩種,一種任務是同步的,一種任務是網絡請求,發送操作是同步的,但是請求到的結果是異步的。阿里的面試題,肯定是后者異步并發隊列里面的任務也是并發的操作,如何實現ABC三個網絡請求都回來之后->執行D這樣的操作?

異步并發(同步任務)

同步任務很簡單,也是所有你能搜到的資料告訴你如何做到的幾個方法。

第一種GCD group

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務A");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務B");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務C");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任務完成執行");
});

// 2018-04-18 10:18:52.950271+0800 GCD[2283:78081] 同步任務B
// 2018-04-18 10:18:52.950271+0800 GCD[2283:78082] 同步任務C
// 2018-04-18 10:18:52.950273+0800 GCD[2283:78083] 同步任務A
// 2018-04-18 10:18:52.950424+0800 GCD[2283:78082] 任務完成執行

// 2018-04-18 10:19:30.821003+0800 GCD[2315:79354] 同步任務B
// 2018-04-18 10:19:30.821003+0800 GCD[2315:79355] 同步任務A
// 2018-04-18 10:19:30.821003+0800 GCD[2315:79370] 同步任務C
// 2018-04-18 10:19:30.821145+0800 GCD[2315:79355] 任務完成執行
可以看到,異步線程,并發隊列里面,三個任務可以同時執行,因此打印順序隨機。但是由于打印的任務,這個打印的動作是同步的,沒有再開線程有其他進一步的異步操作,所以你看起來好像沒什么問題。

第二種 dispatch_barrier_async

dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務A");
});

dispatch_async(queue, ^{
    NSLog(@"任務B");
});

dispatch_async(queue, ^{
    NSLog(@"任務C");
});

dispatch_barrier_async(queue, ^{
    NSLog(@"阻塞自定義并發隊列");
});

dispatch_async(queue, ^{
    NSLog(@"任務D");
});

dispatch_async(queue, ^{
    NSLog(@"任務E");
});

注意,這里用到的dispatch_barrier_async如果使用的隊列是dispatch_global_queue,那么就等同意dispatch_async,起不到阻塞的作用。我們需要自己創建并發隊列,然后再執行barrier函數,前面ABC三個任務隨機,后面DE隨機,但是DE的執行必須是等待ABC任務執行完的。

第三種 NSOperation

NSBlockOperation *operatioon1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務A");
}];

NSBlockOperation *operatioon2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任務B");
}];

NSBlockOperation *operatioon3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任務C");
}];

NSBlockOperation *operatioon4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任務D");
}];

[operatioon4 addDependency:operatioon1];
[operatioon4 addDependency:operatioon2];
[operatioon4 addDependency:operatioon3];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operatioon1,operatioon2,operatioon3,operatioon4] waitUntilFinished:YES];
NSLog(@"完成之后的操作");

通過任務之間的依賴關系執行,最后的參數YES的時候是會阻塞當前線程,執行完之后再往后執行,NO的話就不阻塞

可以看到,上面的都是異步并發操作,而里面的任務是同步的,這里的任務指的是Block里面的所有操作,但是如果Block里面的操作是網絡請求,也是異步的,那上面的做法就會有問題了,看如下代碼

錯誤例子

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務A");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡異步任務AA");
    });
});

dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務B");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務C");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任務完成執行");
});

// 2018-04-18 10:53:16.294263+0800 GCD[3287:115681] 同步任務B
// 2018-04-18 10:53:16.294263+0800 GCD[3287:115679] 同步任務C
// 2018-04-18 10:53:16.294263+0800 GCD[3287:115699] 同步任務A
// 2018-04-18 10:53:16.294430+0800 GCD[3287:115699] 任務完成執行
// 2018-04-18 10:53:18.294592+0800 GCD[3287:115646] 網絡異步任務AA
用dispatch_after來模擬網絡請求,可以看到,同步任務ABC->D這些操作還是正確的,但是里面有個模擬網絡請求的任務,就不會按我們所想的順序執行了。為什么呢?很簡單,首先都是異步的,子線程操作,而且是并發隊列,那么任務可以多個一起,如果任務是單純的打印,即同步任務,那么就能完成我們的預期,如果Block任務里面還嵌套異步任務,因為并發隊列里面的任務,只是負責打印和發送請求的操作,異步回調數據是不歸隊列管的,任務的執行完畢,只是Block代碼塊代碼執行完,如果里面還包含異步任務,這里就需要通過信號量dispatch_semaphore來實現了。

下面就來實現如果并發隊列里面的任務是網絡請求,如何等ABC三個網絡請求回調之后,再執行D?

先來介紹下Dispatch_semaphore的使用

先來介紹下Dispatch_semaphore的使用

dispatch_semaphore是GCD用來同步的一種方式,與他相關的共有三個函數,分別是

dispatch_semaphore_create,
dispatch_semaphore_signal,
dispatch_semaphore_wait。
下面我們逐一介紹三個函數:

dispatch_semaphore_create

dispatch_semaphore_t dispatch_semaphore_create(long value);

傳入的參數為long,輸出一個dispatch_semaphore_t類型且值為value的信號量。

值得注意的是,這里的傳入的參數value必須大于或等于0,否則dispatch_semaphore_create會返回NULL。

關于信號量,我就不在這里累述了,網上很多介紹這個的。我們這里主要講一下dispatch_semaphore這三個函數的用法)。
dispatch_semaphore_signal的聲明為:

long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

這個函數會使傳入的信號量dsema的值加1;(至于返回值,待會兒再講)
dispatch_semaphore_wait的聲明為:

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

這個函數會使傳入的信號量dsema的值減1;

這個函數的作用是這樣的,如果dsema信號量的值大于0,該函數所處線程就繼續執行下面的語句,并且將信號量的值減1;

如果desema的值為0,那么這個函數就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,

不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,

且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續向下執行并將信號量減1。

如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執行其后語句。
dispatch_semaphore_signal的返回值為long類型

當返回值為0時表示當前并沒有線程等待其處理的信號量,其處理

的信號量的值加1即可。當返回值不為0時,表示其當前有(一個或多個)線程等待其處理的信號量,并且該函數喚醒了一個等待的線程(當線程有優先級時,喚醒優先級最高的線程;否則隨機喚醒)。

dispatch_semaphore_wait的返回值也為long型。當其返回0時表示在timeout之前,該函數所處的線程被成功喚醒。
當其返回不為0時,表示timeout發生。

在設置timeout時,比較有用的兩個宏:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。

DISPATCH_TIME_NOW  表示當前;

DISPATCH_TIME_FOREVER  表示遙遠的未來;

一般可以直接設置timeout為這兩個宏其中的一個,或者自己創建一個dispatch_time_t類型的變量。

創建dispatch_time_t類型的變量有兩種方法,dispatch_time和dispatch_walltime。

利用創建dispatch_time創建dispatch_time_t類型變量的時候一般也會用到這兩個變量。

dispatch_time的聲明如下:

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
其參數when需傳入一個dispatch_time_t類型的變量,和一個delta值。表示when加delta時間就是timeout的時間。
例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);

表示當前時間向后延時一秒為timeout的時間。
關于信號量,一般可以用停車來比喻。

停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待。

信號量的值就相當于剩余車位的數目,dispatch_semaphore_wait函數就相當于來了一輛車,dispatch_semaphore_signal

就相當于走了一輛車。停車位的剩余數目在初始化的時候就已經指明了(dispatch_semaphore_create(long value)),

調用一次dispatch_semaphore_signal,剩余的車位就增加一個;調用一次dispatch_semaphore_wait剩余車位就減少一個;

當剩余車位為0時,再來車(即調用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主

沒有耐心,給自己設定了一段等待時間,這段時間內等不到停車位就走了,如果等到了就開進去停車。而有些車主就像把車停在這,

所以就一直等下去。
參考文章 這里面有個Demo,理解不了的可以看一下,信號量1的時候一般都當做鎖來看待

異步并發(模擬網絡異步任務)
子線程同時執行ABC三個同步任務、全部執行完成再在子線程執行三個同步任務D。

說隊列組/依賴基本可以確定了解GCD/NSOpertion。但是比較麻煩、用線程柵欄dispatch_barrier的話會更簡便一些

這個就是上面的同步介紹面試題

上一題中的ABC三個任務改成異步任務(如AFN網絡請求)、全部回調成功后進行數據整合。
如果只說隊列/任務組肯定不行。因為網絡請求本身是異步的、任務會立即完成、但數據還沒有回來。
最好的就是在隊列組的前提下。把異步的網絡請求轉化為同步、以捕獲正確的完成時機。
具體操作需要使用信號量。

這里有個帖子有介紹這個,但是沒有具體Demo,下面看看如何組合信號量實現的

第一種 dispatch_group + semaphore

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任務A");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"網絡異步任務一");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});

dispatch_group_async(group, queue, ^{
    
    NSLog(@"同步任務B");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡異步任務二");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務C");
});

dispatch_group_async(group, queue, ^{
    
    NSLog(@"同步任務D");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡異步任務四");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任務完成執行");
});

分析如下:

異步并發隊列,里面添加了四個任務,任務AB有網絡請求,C同步任務打印,D也有網絡請求,如何在ABCD四個任務完成,而且網絡請求也完成之后執行之后的操作?

可以看到,Block整個就是一個任務,如果沒有dispatch_semaphore_wait,帶有網絡請求的任務,因為網絡請求本身是異步的、任務會立即完成、但數據還沒有回來。,因為發送網絡請求,就已經把Block的任務完成了,異步回來的操作已經不屬于并發隊列里面的管理的任務了。就和上面的錯誤例子一樣,完成任務的執行,和請求回調沒有任何順序關系了。那么如果我們在Block里面加了dispatch_semaphore_wait,什么意思呢?如果信號量為0的時候,那么就會一直在這里等待。可以理解為一開始發送網絡請求出去,這個時候執行到wait函數,信號量為0,等待,隊列任務沒有執行完,只有當請求回來的時候調用singnal的時候,信號+1,wait的函數隨機獲取到信號,放開任務,執行完畢一個,剩下的沒有獲取到的信號繼續等待,那么就會按我們的,執行完一個網絡請求,信號+1,釋放一個wait,執行完一個Block任務,那么,當所有的網絡請求執行完,所有的wait都被釋放,任務都完成了,才會通知Group調用完成。

第二種 dispatch_group_enter 和 dispatch_group_leave

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"同步任務A");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"網絡異步任務一");
dispatch_group_leave(group);
});
});

dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    
    NSLog(@"同步任務B");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡異步任務二");
        dispatch_group_leave(group);
    });
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    NSLog(@"同步任務C");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    
    NSLog(@"同步任務D");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡異步任務四");
        dispatch_group_leave(group);
    });
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任務完成執行");
});

這種方法也是我之前項目中用的,也能實現上面的執行順序。

ABCD異步順序隨機,無論任務是否有異步,都會等任務執行完(包括網絡請求)再執行任務完成

異步串行(同步任務)
這個沒什么好說的,同步任務,在串行隊列,肯定順序執行

異步串行(異步任務)
但是如果串行隊列里面的任務是網絡請求如何,再讓等網絡請求回調之后也順序執行?

看代碼 不做處理

dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"執行任務一");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"網絡任務一");
});
});

dispatch_async(queue, ^{
    NSLog(@"執行任務二");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡任務二");
    });
});

dispatch_async(queue, ^{
    NSLog(@"執行任務三");
});

dispatch_async(queue, ^{
    NSLog(@"執行完成");
});
2018-04-18 12:11:04.956900+0800 GCD[4889:182340] 執行任務一
2018-04-18 12:11:04.957076+0800 GCD[4889:182340] 執行任務二
2018-04-18 12:11:04.957183+0800 GCD[4889:182340] 執行任務三
2018-04-18 12:11:04.957290+0800 GCD[4889:182340] 執行完成
2018-04-18 12:11:05.557397+0800 GCD[4889:182291] 網絡任務二
2018-04-18 12:11:07.156658+0800 GCD[4889:182291] 網絡任務一

可以看到串行隊列任務執行是順序的,但是異步網絡請求回調是不按順序的。下面我們用dispatch_semaphore處理下

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"執行任務一");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"網絡任務一");
dispatch_semaphore_signal(semaphore);
});
});

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"執行任務二");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"網絡任務二");
        dispatch_semaphore_signal(semaphore);
    });
});

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"執行任務三");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"執行完成");
    dispatch_semaphore_signal(semaphore);
});

2018-04-18 13:54:20.412918+0800 GCD[6841:262952] 執行任務一
2018-04-18 13:54:22.413131+0800 GCD[6841:262916] 網絡任務一
2018-04-18 13:54:22.413369+0800 GCD[6841:262952] 執行任務二
2018-04-18 13:54:23.073214+0800 GCD[6841:262916] 網絡任務二
2018-04-18 13:54:23.073512+0800 GCD[6841:262952] 執行任務三
2018-04-18 13:54:23.073697+0800 GCD[6841:262952] 執行完成

當異步串行隊列,而且任務有網絡異步請求,我們需要用信號量,每次開放一個任務,當一個任務無論是同步還是異步,都是操作完之后再把信號量+1,然后下一個任務才會解鎖。這里的信號量可以當做鎖來理解。

總結

異步并發執行同步任務,可以用dispatch_group,dispatch_barrier和NSOperation的依賴

異步串行執行同步任務,默認就是順序執行

異步并發執行異步任務,可以用dispatch_group+semaphore 初始化信號為0,執行完一個異步通過singnal釋放一個wait任務

或者用disaptch_group_enter 和 dispatch_group_leave

異步串行執行異步任務,可以用dispatch_semaphore 初始化信號為1,順序執行任務,每個任務都加鎖,執行任務消耗鎖,順延任務等待,執行完一個任務,singnal,然后下一個任務開鎖順序執行

如果多個任務網絡請求,NSOperation我感覺無法實現 多網絡任務執行完之后再最終統一執行某一個人任務。這個時候只有用dispatch_group + semaphore來實現

多線程面試題

一個簡書的好文章

dispatch_semaphore

NSOperation
————————————————

原文鏈接:https://blog.csdn.net/Deft_MKJing/article/details/51518556

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容