每日一問13——多線程之NSOperation與NSOperationQueue

NSOperation

看一下官方的介紹:

The NSOperation class itself is an abstract base class that must be subclassed in order to do any useful work.

NSOperation是一個抽象基類,我們需要使用它的子類來完成工作。從使用上來說NSOperation就是對GCDblock的封裝,它表示一個要被執(zhí)行的任務(wù)。
GCD相比,NSOperation可以更好的控制任務(wù),包括暫停,繼續(xù),取消這類操作并且提供了當(dāng)前任務(wù)的狀態(tài)供開發(fā)者監(jiān)聽。而在GCD中要實現(xiàn)這些需求可能會需要其他很多復(fù)雜的代碼。

NSOperation基本功能
1.基本的操作和狀態(tài)

先看一下NSOperation.h中的NSOperation類。

- (void)start;
- (void)main;

@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;

NSOperation提供了任務(wù)的狀態(tài),執(zhí)行,完畢,取消等。并且提供了一個start方法,這表明NSOperation是可以直接執(zhí)行的。這里要注意NSOperation執(zhí)行是一個同步操作,任務(wù)會執(zhí)行在當(dāng)前運行的線程上。

2.依賴關(guān)系
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

@property (readonly, copy) NSArray<NSOperation *> *dependencies;

NSOperation還提供了一個依賴功能,通過依賴可以控制任務(wù)執(zhí)行的順序。

3.任務(wù)優(yōu)先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;

GCD不同,GCD只能設(shè)置隊列的優(yōu)先級,而NSOperation及其子類可以設(shè)置任務(wù)的優(yōu)先級。需要注意的是,NSOperationQueue也不能完全保證優(yōu)先級高的任務(wù)一定先執(zhí)行。

系統(tǒng)提供的子類
NSInvocationOperation

這個類的使用類似于給UIbutton添加一個方法。需要一個對象和一個Selector。

NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
NSLog(@"begin");
[inOp start];
NSLog(@"end");

- (void)testInOp {
    NSLog(@"%@",[NSThread currentThread]);
}

打印結(jié)果:

2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] begin
2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] <NSThread: 0x60000006e700>{number = 1, name = main}
2017-09-19 16:12:55.939 learn_09_NSOperation[3507:146749] end

可以看出,這個任務(wù)相是同步執(zhí)行的。再修改一下例子:

NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
    NSLog(@"begin");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [inOp start];
    });
    NSLog(@"end");

打印結(jié)果

2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] begin
2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] end
2017-09-19 16:14:44.267 learn_09_NSOperation[3536:147761] <NSThread: 0x60000007c240>{number = 3, name = (null)}

可以看出,任務(wù)可以直接在子線程啟動,就變成一個異步執(zhí)行的任務(wù)。其實這里還是在其他線程中同步執(zhí)行了該任務(wù)

NSBlockOperation

與之前的NSInvocationOperation比較,NSBlockOperation在使用上更加的方便,它提供一個block代碼塊來執(zhí)行任務(wù)。

NSBlockOperation *bOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
[bOp start];

執(zhí)行結(jié)果:<NSThread: 0x60800006f280>{number = 1, name = main}
我們可以看出基本使用上與NSInvocationOperation結(jié)果一致。但不同的是NSBlockOperation支持了并發(fā)執(zhí)行一個或多個block。

NSBlockOperation *bOp = [[NSBlockOperation alloc] init];
    [bOp addExecutionBlock:^{
        NSLog(@"1--%@",[NSThread currentThread]);
        NSLog(@"2--%@",[NSThread currentThread]);
        NSLog(@"3--%@",[NSThread currentThread]);
    }];
    [bOp addExecutionBlock:^{
        NSLog(@"4--%@",[NSThread currentThread]);
        NSLog(@"5--%@",[NSThread currentThread]);
        NSLog(@"6--%@",[NSThread currentThread]);
    }];
    [bOp addExecutionBlock:^{
        NSLog(@"7--%@",[NSThread currentThread]);
        NSLog(@"8--%@",[NSThread currentThread]);
        NSLog(@"9--%@",[NSThread currentThread]);
    }];
    [bOp start];

執(zhí)行結(jié)果:

2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 1--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 2--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 3--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152091] 4--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152089] 7--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152091] 5--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152089] 8--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152091] 6--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152089] 9--<NSThread: 0x608000266300>{number = 4, name = (null)}

從中我們可以看出每一個代碼塊執(zhí)行在一條線程上,每條線程之間彼此無關(guān),任務(wù)執(zhí)行順序是并發(fā)執(zhí)行的。

為了更好的操作這些任務(wù),我們需要結(jié)合NSOperationQueue來管理這些任務(wù)。這樣,我們任務(wù)隊列的概念就結(jié)合起來了。

自定義NSOperation的子類

自定義串行NSOperation,只需要在子類中重寫main函數(shù)
自定義并行NSOperation,需要子類實現(xiàn)start,main,isExecuting,isFinished,isConcurrent,isAsynchronous方法
一般來說我們使用系統(tǒng)提供的子類就可以完成絕大部分需求了。關(guān)于自定義子類可參見這篇文章NSOperation 自定義子類實現(xiàn)非并發(fā)和并發(fā)操作

NSOperationQueue

1.隊列的基本使用

NSOperationQueue類似于GCD中的隊列。我們知道GCD中的隊列有三種:主隊列串行隊列和并發(fā)隊列NSOperationQueue更簡單,只有兩種:主隊列非主隊列

默認(rèn)情況下,我們創(chuàng)建的NSOperationQueue隊列都是非主隊列。
我們可以通過NSOperationQueue.mainQueue來獲取主隊列。

NSOperationQueue的主隊列是串行隊列,而且其中所有NSOperation都會在主線程中執(zhí)行。

對于非主隊列來說,一旦一個NSOperation被放入其中,那這個NSOperation一定是并發(fā)執(zhí)行的。因為NSOperationQueue會為每一個NSOperation創(chuàng)建線程并調(diào)用它的start方法。
但這并不意味著NSOperationQueue不支持串行執(zhí)行。NSOperationQueue提供了maxConcurrentOperationCount屬性,讓我們設(shè)置隊列中的最大并發(fā)數(shù)。當(dāng)設(shè)置為1時,隊列會按串行方式執(zhí)行里面的任務(wù)。

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    for (int i=0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];
        
        [queue addOperation:bop];
    }

打印結(jié)果:

2017-09-19 16:38:09.401 learn_09_NSOperation[3763:158562] 0----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 1----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 2----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 3----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 4----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 5----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 6----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 7----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 8----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.407 learn_09_NSOperation[3763:158562] 9----<NSThread: 0x608000079500>{number = 3, name = (null)}

從結(jié)果可以看出,在最大并發(fā)為1的情況下,隊列是異步的串行執(zhí)行的。當(dāng)然如果我們不設(shè)置最大并發(fā)數(shù)或者>1的情況下,系統(tǒng)會自動為每個任務(wù)分配線程,所有的線程調(diào)度都交給系統(tǒng)控制。

2.任務(wù)管理

由于我們的NSOperation對象擁有自己的任務(wù)狀態(tài),為了管理隊列中的NSOperation任務(wù),系統(tǒng)為我們提供了以下方法來獲取隊列中的任務(wù)。

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);

我們可以獲取到隊列中的每一個任務(wù)并操作它。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];

        [queue addOperation:bop];
    }
    NSBlockOperation *op = queue.operations.lastObject;
   NSLog(@"%d",op.isCancelled);
    [op cancel];
    NSLog(@"%d",op.isCancelled);

在執(zhí)行前將任務(wù)取消掉,則該任務(wù)將不會被執(zhí)行。

當(dāng)然,在任務(wù)較多的情況下,這樣取消任務(wù)實在是太可愛了。NSOperationQueue 提供了cancelAllOperations方法,幫我們直接取消掉所有的任務(wù)。

3.其他操作
隊列優(yōu)先級

像CGD一樣,NSOperationQueue同樣提供了優(yōu)先級設(shè)置,我們可以根據(jù)不同場景選擇對應(yīng)的優(yōu)先級。

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    /* UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. */
    NSQualityOfServiceUserInteractive = 0x21,
    
    /* UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction.  For example, loading an email after a user has selected it in a message list. */
    NSQualityOfServiceUserInitiated = 0x19,
    
    /* Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results.  This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator.  This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained.  For example, periodic content updates or bulk file operations such as media import. */
    NSQualityOfServiceUtility = 0x11,
    
    /* Background QoS is used for work that is not user initiated or visible.  In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work.  For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. */
    NSQualityOfServiceBackground = 0x09,

    /* Default QoS indicates the absence of QoS information.  Whenever possible QoS information will be inferred from other sources.  If such inference is not possible, a QoS between UserInitiated and Utility will be used. */
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
等待執(zhí)行
- (void)waitUntilAllOperationsAreFinished

NSOperationQueue提供了這樣一個函數(shù),讓調(diào)用該函數(shù)前的所有任務(wù)執(zhí)行完畢以后才會執(zhí)行后續(xù)的操作

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 3; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];
        [queue addOperation:bop];
    }
    NSLog(@"wait");
    [queue waitUntilAllOperationsAreFinished];
    NSLog(@"go on");

打印結(jié)果:

2017-09-19 16:58:22.864 learn_09_NSOperation[3908:166871] wait
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166906] 1----<NSThread: 0x6000002620c0>{number = 3, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166907] 2----<NSThread: 0x608000078940>{number = 5, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166909] 0----<NSThread: 0x600000262600>{number = 4, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166871] go on

當(dāng)任務(wù)執(zhí)行完畢后,才會繼續(xù)執(zhí)行waitUntilAllOperationsAreFinished下面的代碼,可以看出waitUntilAllOperationsAreFinished會阻塞當(dāng)前的線程來等待隊列中任務(wù)執(zhí)行。

暫停隊列

NSOperationQueue 提供了suspended的屬性,顧名思義這個屬性用來控制隊列是否暫停。但它的暫停方法并不是那么直接。官方文檔上是這么說的:

如果這個值設(shè)置為 NO,那說明這個隊列已經(jīng)準(zhǔn)備好了可以執(zhí)行了。如果這個值設(shè)置為 YES,那么已經(jīng)添加到隊列中的操作還是可以執(zhí)行了,而后面繼續(xù)添加進(jìn)隊列中的操作才處于暫停狀態(tài),直到你再次將這個值設(shè)置為 NO 時,后面加入的操作才會繼續(xù)執(zhí)行。這個屬性的默認(rèn)值是 NO。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 4; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d",i);
        }];
        if(i == 2) {
            [queue setSuspended:YES];
        }
        [queue addOperation:bop];
    }
//模擬耗時操作后開啟隊列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"re start");
        [queue setSuspended:NO];
    });

打印結(jié)果:

2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171960] 0
2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171959] 1
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171922] re start
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171959] 2
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171960] 3
一個例子:利用任務(wù)依賴實現(xiàn)一個規(guī)定順序的任務(wù)隊列

假設(shè)我們10個任務(wù),我們需要異步執(zhí)行,先順序執(zhí)行后5條任務(wù)再順序執(zhí)行前5條任務(wù)。通過NSOperation的依賴功能與NSOperationQueue結(jié)合來滿足這個需求。(代碼寫得不好,只是為了表現(xiàn)NSOperationQueue的使用靈活與簡便)

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSMutableArray *ops = [NSMutableArray array];
    for (int i = 0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d---%@",i,[NSThread currentThread]);
        }];
        [ops addObject:bop];
    }
    
//
    [ops enumerateObjectsUsingBlock:^(NSBlockOperation *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if(idx > 0 && idx < 5){
            NSBlockOperation *old = ops[idx - 1];
            [obj addDependency:old];
        }
        else if (idx > 5) {
            NSBlockOperation *old = ops[idx - 1];
            [obj addDependency:old];
        }
        if(idx == ops.count - 1) {
            NSBlockOperation *fst = ops.firstObject;
            [fst addDependency:obj];
        }
    }];
    
    [queue addOperations:ops waitUntilFinished:NO];

打印結(jié)果:

2017-09-19 17:16:33.256 learn_09_NSOperation[4037:174132] 5---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.258 learn_09_NSOperation[4037:174132] 6---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.260 learn_09_NSOperation[4037:174132] 7---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.261 learn_09_NSOperation[4037:174134] 8---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.262 learn_09_NSOperation[4037:174134] 9---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.265 learn_09_NSOperation[4037:174134] 0---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.283 learn_09_NSOperation[4037:174134] 1---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.292 learn_09_NSOperation[4037:174134] 2---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.298 learn_09_NSOperation[4037:174132] 3---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.299 learn_09_NSOperation[4037:174134] 4---<NSThread: 0x608000262d80>{number = 4, name = (null)}

可以看出先依次執(zhí)行了5-9,再依次執(zhí)行了0-4。并且在設(shè)置了依賴關(guān)系后,隊列的線程調(diào)度也會自動變化。保證了程序性能。

NSOperation與GCD比較

NSOperation是CGD的封裝,相比于GCD而言,NSOperation可以更加方便的控制任務(wù),設(shè)置依賴,執(zhí)行,暫停,取消,控制優(yōu)先級等。NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結(jié)構(gòu)更好,子類化NSOperation的設(shè)計思路,是具有面向?qū)ο蟮膬?yōu)點(復(fù)用、封裝),使得實現(xiàn)是多線程支持,而接口簡單。

GCD以block為單位,代碼簡潔。同時GCD中的隊列、組、信號量、source、barriers都是組成并行編程的基本原語。對于一次性的計算,或是僅僅為了加快現(xiàn)有方法的運行速度,選擇輕量化的GCD就更加方便。

小結(jié)

通過NSOperation與NSOperationQueue的結(jié)合使用,我們可以實現(xiàn)與GCD相同的多線程并發(fā)操作,并且能夠更方便的對異步任務(wù)進(jìn)行管理控制。

相關(guān)文章

還在用GCD?來看看 NSOperation 吧
NSOperation v.s GCD
iOS多線程編程總結(jié)
iOS多線程(三):NSOperationQueue 的使用

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

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