iOS多線程到底不安全在哪里旺上?

復(fù)制粘貼文: 原文鏈接:http://www.cocoachina.com/articles/18211

https://mp.weixin.qq.com/s?__biz=MjM5OTM0MzIwMQ==&mid=2652547923&idx=1&sn=d2d69b28adfc77b1544fef9ad9052369&chksm=bcd2ee5d8ba5674ba57f94facf06b2da6b71fe8e53cdeb0eb6e9ff0e643cf635e1107133e657&scene=38&key=e3b280559df2aa04ca85b951fe5953247d1b4ee36cddaa10471a1321006bec02bb2e0bd489c90bfbbe8b94dfa953fb74d5b39f896355e71c6601c7e837eed2f09a0265c6e67a8cebbef27a9033dc1007&ascene=0&uin=MjU5Nzc5MDQyMw%3D%3D&devicetype=iMac+MacBookPro12%2C1+OSX+OSX+10.14.2+build(18C54)&version=12031810&nettype=WIFI&lang=zh_CN&fontScale=100&pass_ticket=M1h9p5wPcLx45ZA7B2XxjoFheAS9c4ZCDs8%2B%2BEH2Gcca%2FVdNZTw3iLn59yiDkBld##

iOS多線程安全的概念在很多地方都會(huì)遇到瓶蚂,為什么不安全宣吱,不安全又該怎么去定義,其實(shí)是個(gè)值得深究的話題征候。

共享狀態(tài)祟敛,多線程共同訪問某個(gè)對(duì)象的property兆解,在iOS編程里是很普遍的使用場(chǎng)景,我們就從Property的多線程安全說起锅睛。

Property

當(dāng)我們討論property多線程安全的時(shí)候,很多人都知道給property加上atomic attribute之后辣垒,可以一定程度的保障多線程安全,類似:

@property (atomic, strong) NSString*                 userName;

事情并沒有看上去這么簡(jiǎn)單勋桶,要分析property在多線程場(chǎng)景下的表現(xiàn)侥猬,需要先對(duì)property的類型做區(qū)分。

我們可以簡(jiǎn)單的將property分為值類型和對(duì)象類型陵究,值類型是指primitive type,包括int, long, bool等非對(duì)象類型仪召,另一種是對(duì)象類型,聲明為指針扔茅,可以指向某個(gè)符合類型定義的內(nèi)存區(qū)域秸苗。

上述代碼中userName明顯是個(gè)對(duì)象類型,當(dāng)我們?cè)L問userName的時(shí)候惊楼,訪問的有可能是userName本身,也有可能是userName所指向的內(nèi)存區(qū)域雅倒。

比如:

self.userName = @"peak";

是在對(duì)指針本身進(jìn)行賦值。而

[self.userName rangeOfString:@"peak"];

是在訪問指針指向的字符串所在的內(nèi)存區(qū)域蔑匣,這二者并不一樣。

所以我們可以大致上將property分為三類:

[圖片上傳失敗...(image-630b1a-1604474980222)]

分完類之后凿将,我們需要明白這三類property的內(nèi)存模型。

Memory Layout

當(dāng)我們討論多線程安全的時(shí)候丸相,其實(shí)是在討論多個(gè)線程同時(shí)訪問一個(gè)內(nèi)存區(qū)域的安全問題彼棍。針對(duì)同一塊區(qū)域膳算,我們有兩種操作,讀(load)和寫(store)华匾,讀和寫同時(shí)發(fā)生在同一塊區(qū)域的時(shí)候,就有可能出現(xiàn)多線程不安全蜘拉。所以展開討論之前有鹿,先要明白上述三種property的內(nèi)存模型,可用如下圖示:

[圖片上傳失敗...(image-a7a2aa-1604474980222)]

以64位系統(tǒng)為例葱跋,指針NSString*是8個(gè)字節(jié)的內(nèi)存區(qū)域,int count是個(gè)4字節(jié)的區(qū)域娱俺,而@“Peak”是一塊根據(jù)字符串長(zhǎng)度而定的內(nèi)存區(qū)域。

當(dāng)我們?cè)L問property的時(shí)候模庐,實(shí)際上是訪問上圖中三塊內(nèi)存區(qū)域油宜。

self.userName = @"peak";

是修改第一塊區(qū)域。

self.count = 10;

是在修改第二塊區(qū)域慎冤。

[self.userName rangeOfString:@"peak"];

是在讀取第三塊區(qū)域。

不安全的定義

明白了property的類型以及他們對(duì)應(yīng)的內(nèi)存模型粪薛,我們?cè)賮砜纯床话踩亩x。Wikipedia如是說:

A piece of code is thread-safe if it manipulates shared data structures only in a manner that guarantees safe execution by multiple threads at the same time

這段定義看起來還是有點(diǎn)抽象湃交,我們可以將多線程不安全解釋為:多線程訪問時(shí)出現(xiàn)意料之外的結(jié)果。這個(gè)意料之外的結(jié)果包含幾種場(chǎng)景搞莺,不一定是指crash,后面再一一分析迈喉。

先來看下多線程是如何同時(shí)訪問內(nèi)存的。不考慮CPU cache對(duì)變量的緩存挨摸,內(nèi)存訪問可以用下圖表示:

[圖片上傳失敗...(image-b2de2b-1604474980222)]

從上圖中可以看出岁歉,我們只有一個(gè)地址總線,一個(gè)內(nèi)存锅移。即使是在多線程的環(huán)境下,也不可能存在兩個(gè)線程同時(shí)訪問同一塊內(nèi)存區(qū)域的場(chǎng)景置逻,內(nèi)存的訪問一定是通過一個(gè)地址總線串行排隊(duì)訪問的,所以在繼續(xù)后續(xù)之前诽偷,我們先要明確幾個(gè)結(jié)論:

結(jié)論一:內(nèi)存的訪問時(shí)串行的疯坤,并不會(huì)導(dǎo)致內(nèi)存數(shù)據(jù)的錯(cuò)亂或者應(yīng)用的crash。

結(jié)論二:如果讀寫(load or store)的內(nèi)存長(zhǎng)度小于等于地址總線的長(zhǎng)度压怠,那么讀寫的操作是原子的,一次完成菌瘫。比如bool,int雇盖,long在64位系統(tǒng)下的單次讀寫都是原子操作。

接下來我們根據(jù)上面三種property的分類逐一看下多線程的不安全場(chǎng)景崔挖。

值類型Property

先以BOOL值類型為例,當(dāng)我們有兩個(gè)線程訪問如下property的時(shí)候:

@property (nonatomic, assgin) BOOL    isDeleted;

//thread 1
bool isDeleted = self.isDeleted;

//thread 2
self.isDeleted = false;

線程1和線程2狸相,一個(gè)讀(load),一個(gè)寫(store)脓鹃,對(duì)于BOOL isDeleted的訪問可能有先后之分,但一定是串行排隊(duì)的瘸右。而且由于BOOL大小只有1個(gè)字節(jié),64位系統(tǒng)的地址總線對(duì)于讀寫指令可以支持8個(gè)字節(jié)的長(zhǎng)度苞俘,所以對(duì)于BOOL的讀和寫操作我們可以認(rèn)為是原子的,所以當(dāng)我們聲明BOOL類型的property的時(shí)候,從原子性的角度看襟诸,使用atomic和nonatomic并沒有實(shí)際上的區(qū)別(當(dāng)然如果重載了getter方法就另當(dāng)別論了)。

如果是int類型呢歌亲?

@property (nonatomic, assgin) int    count;

//thread 1
int curCount = self.count;

//thread 2
self.count = 1;

同理int類型長(zhǎng)度為4字節(jié),讀和寫都可以通過一個(gè)指令完成惋鸥,所以理論上讀和寫操作都是原子的。從訪問內(nèi)存的角度看nonatomic和atomic也并沒有什么區(qū)別卦绣。

atomic到底有什么用呢飞蚓?據(jù)我所知,用處有二:

  • 用處一: 生成原子操作的getter和setter趴拧。

設(shè)置atomic之后,默認(rèn)生成的getter和setter方法執(zhí)行是原子的著榴。也就是說,當(dāng)我們?cè)诰€程1執(zhí)行g(shù)etter方法的時(shí)候(創(chuàng)建調(diào)用棧暮胧,返回地址锐借,出棧)瞎饲,線程B如果想執(zhí)行setter方法,必須先等getter方法完成才能執(zhí)行嗅战。舉個(gè)例子俺亮,在32位系統(tǒng)里,如果通過getter返回64位的double脚曾,地址總線寬度為32位,從內(nèi)存當(dāng)中讀取double的時(shí)候無法通過原子操作完成本讥,如果不通過atomic加鎖,有可能會(huì)在讀取的中途在其他線程發(fā)生setter操作色查,從而出現(xiàn)異常值。如果出現(xiàn)這種異常值秧了,就發(fā)生了多線程不安全。

  • 用處二:設(shè)置Memory Barrier

對(duì)于Objective C的實(shí)現(xiàn)來說验毡,幾乎所有的加鎖操作最后都會(huì)設(shè)置memory barrier帝嗡,atomic本質(zhì)上是對(duì)getter,setter加了鎖哟玷,所以也會(huì)設(shè)置memory barrier。官方文檔表述如下:

Note: Most types of locks also incorporate a memory barrier to ensure that any preceding load and store instructions are completed before entering the critical section.

memory barrier有什么用處呢碗降?

memory barrier能夠保證內(nèi)存操作的順序,按照我們代碼的書寫順序來动看。聽起來有點(diǎn)不可思議,事實(shí)是編譯器會(huì)對(duì)我們的代碼做優(yōu)化菱皆,在它認(rèn)為合理的場(chǎng)景改變我們代碼最終翻譯成的機(jī)器指令順序。也就是說如下代碼:

self.intA = 0;  //line 1
self.intB = 1; //line 2

編譯器可能在一些場(chǎng)景下先執(zhí)行l(wèi)ine2仇轻,再執(zhí)行l(wèi)ine1,因?yàn)樗J(rèn)為A和B之間并不存在依賴關(guān)系篷店,雖然在代碼執(zhí)行的時(shí)候,在另一個(gè)線程intA和intB存在某種依賴疲陕,必須要求line1先于line2執(zhí)行。

如果設(shè)置property為atomic携茂,也就是設(shè)置了memory barrier之后,就能夠保證line1的執(zhí)行一定是先于line2的讳苦,當(dāng)然這種場(chǎng)景非常罕見,一則是出現(xiàn)變量跨線程訪問依賴鸳谜,二是遇上編譯器的優(yōu)化逮京,兩個(gè)條件缺一不可束莫。這種極端的場(chǎng)景下,atomic確實(shí)可以讓我們的代碼更加多線程安全一點(diǎn)览绿,但我寫iOS代碼至今,還未遇到過這種場(chǎng)景饿敲,較大的可能性是編譯器已經(jīng)足夠聰明,在我們需要的地方設(shè)置memory barrier了怀各。

是不是使用了atomic就一定多線程安全呢?我們可以看看如下代碼:

@property (atomic, assign)    int       intA;

//thread A
for (int i = 0; i < 10000; i ++) {
    self.intA = self.intA + 1;
    NSLog(@"Thread A: %d
", self.intA);
}

//thread B
for (int i = 0; i < 10000; i ++) {
    self.intA = self.intA + 1;
    NSLog(@"Thread B: %d
", self.intA);
}

即使我將intA聲明為atomic寿酌,最后的結(jié)果也不一定會(huì)是20000。原因就是因?yàn)閟elf.intA = self.intA + 1;不是原子操作醇疼,雖然intA的getter和setter是原子操作,但當(dāng)我們使用intA的時(shí)候秧荆,整個(gè)語句并不是原子的,這行賦值的代碼至少包含讀取(load)乙濒,+1(add),賦值(store)三步操作琉兜,當(dāng)前線程store的時(shí)候可能其他線程已經(jīng)執(zhí)行了若干次store了,導(dǎo)致最后的值小于預(yù)期值豌蟋。這種場(chǎng)景我們也可以稱之為多線程不安全。

指針Property

指針Property一般指向一個(gè)對(duì)象允睹,比如:

@property (atomic, strong) NSString*                 userName;

無論iOS系統(tǒng)是32位系統(tǒng)還是64位,一個(gè)指針的值都能通過一個(gè)指令完成load或者store缭受。但和primitive type不同的是该互,對(duì)象類型還有內(nèi)存管理的相關(guān)操作。在MRC時(shí)代宇智,系統(tǒng)默認(rèn)生成的setter類似如下:

- (void)setUserName:(NSString *)userName {
    if(_uesrName != userName) {
        [userName retain];
        [_userName release];
        _userName = userName;
    }
}

不僅僅是賦值操作,還會(huì)有retain随橘,release調(diào)用。如果property為nonatomic机蔗,上述的setter方法就不是原子操作,我們可以假設(shè)一種場(chǎng)景梆掸,線程1先通過getter獲取當(dāng)前_userName,之后線程2通過setter調(diào)用[_userName release];酸钦,線程1所持有的_userName就變成無效的地址空間了,如果再給這個(gè)地址空間發(fā)消息就會(huì)導(dǎo)致crash钝鸽,出現(xiàn)多線程不安全的場(chǎng)景汇恤。

到了ARC時(shí)代因谎,Xcode已經(jīng)替我們處理了retain和release,絕大部分時(shí)候我們都不需要去關(guān)心內(nèi)存的管理财岔,但retain河爹,release其實(shí)還是存在于最后運(yùn)行的代碼當(dāng)中,atomic和nonatomic對(duì)于對(duì)象類的property聲明理論上還是存在差異咸这,不過我在實(shí)際使用當(dāng)中,將NSString*設(shè)置為nonatomic也從未遇到過上述多線程不安全的場(chǎng)景媳维,極有可能ARC在內(nèi)存管理上的優(yōu)化已經(jīng)將上述場(chǎng)景處理過了,所以我個(gè)人覺得指黎,如果只是對(duì)對(duì)象類property做read,write醋安,atomic和nonatomic在多線程安全上并沒有實(shí)際差別。

指針Property指向的內(nèi)存區(qū)域

這一類多線程的訪問場(chǎng)景是我們很容易出錯(cuò)的地方吓揪,即使我們聲明property為atomic蚁鳖,依然會(huì)出錯(cuò)赁炎。因?yàn)槲覀冊(cè)L問的不是property的指針區(qū)域,而是property所指向的內(nèi)存區(qū)域徙垫。可以看如下代碼:

@property (atomic, strong) NSString*                 stringA;

//thread A
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.stringA = @"a very long string";
    }
    else {
        self.stringA = @"string";
    }
    NSLog(@"Thread A: %@
", self.stringA);
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.stringA.length >= 10) {
        NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
    }
    NSLog(@"Thread B: %@
", self.stringA);
}

雖然stringA是atomic的property姻报,而且在取substring的時(shí)候做了length判斷,線程B還是很容易crash吴旋,因?yàn)樵谇耙豢套xlength的時(shí)候self.stringA = @"a very long string";厢破,下一刻取substring的時(shí)候線程A已經(jīng)將self.stringA = @"string";治拿,立即出現(xiàn)out of bounds的Exception,crash见坑,多線程不安全。

同樣的場(chǎng)景還存在對(duì)集合類操作的時(shí)候荞驴,比如:

@property (atomic, strong) NSArray*                 arr;

//thread A
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
    NSLog(@"Thread A: %@
", self.arr);
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.arr.count >= 2) {
        NSString* str = [self.arr objectAtIndex:1];
    }
    NSLog(@"Thread B: %@
", self.arr);
}

同理贯城,即使我們?cè)谠L問objectAtIndex之前做了count的判斷,線程B依舊很容易crash能犯,原因也是由于前后兩行代碼之間arr所指向的內(nèi)存區(qū)域被其他線程修改了。

所以你看悲雳,真正需要操心的是這一類內(nèi)存區(qū)域的訪問,即使聲明為atomic也沒有用坦胶,我們平常App出現(xiàn)莫名其妙難以重現(xiàn)的多線程crash多是屬于這一類,一旦在多線程的場(chǎng)景下訪問這類內(nèi)存區(qū)域的時(shí)候顿苇,要提起十二分的小心。如何避免這類crash后面會(huì)談到纪岁。

Property多線程安全小結(jié):

簡(jiǎn)而言之则果,atomic的作用只是給getter和setter加了個(gè)鎖,atomic只能保證代碼進(jìn)入getter或者setter函數(shù)內(nèi)部時(shí)是安全的西壮,一旦出了getter和setter,多線程安全只能靠程序員自己保障了款青。所以atomic屬性和使用property的多線程安全并沒什么直接的聯(lián)系。另外,atomic由于加鎖也會(huì)帶來一些性能損耗蔗坯,所以我們?cè)诰帉慽OS代碼的時(shí)候燎含,一般聲明property為nonatomic,在需要做多線程安全的場(chǎng)景瘫镇,自己去額外加鎖做同步。

如何做到多線程安全铣除?

討論到這里,其實(shí)怎么做到多線程安全也比較明朗了尚粘,關(guān)鍵字是atomicity(原子性),只要做到原子性秉继,小到一個(gè)primitive type變量的訪問,大到一長(zhǎng)段代碼邏輯的執(zhí)行尚辑,原子性能保證代碼串行的執(zhí)行,能保證代碼執(zhí)行到一半的時(shí)候杠茬,不會(huì)有另一個(gè)線程介入弛随。

原子性是個(gè)相對(duì)的概念,它所針對(duì)的對(duì)象舀透,粒度可大可小。

比如下段代碼:

if (self.stringA.length >= 10) {
    NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
}

是非原子性的愕够。

但加鎖以后:

//thread A
[_lock lock];
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.stringA = @"a very long string";
    }
    else {
        self.stringA = @"string";
    }
    NSLog(@"Thread A: %@
", self.stringA);
}
[_lock unlock];

//thread B
[_lock lock];
if (self.stringA.length >= 10) {
    NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
}
[_lock unlock];

整段代碼就具有原子性了,就可以認(rèn)為是多線程安全了厉斟。

再比如:

if (self.arr.count >= 2) {
    NSString* str = [self.arr objectAtIndex:1];
}

是非原子性的挚躯。

//thread A
[_lock lock];
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
    NSLog(@"Thread A: %@
", self.arr);
}
[_lock unlock];

//thread B
[_lock lock];
if (self.arr.count >= 2) {
    NSString* str = [self.arr objectAtIndex:1];
}
[_lock unlock];

是具有原子性的。注意漩勤,讀和寫都需要加鎖感挥。

這也是為什么我們?cè)谧龆嗑€程安全的時(shí)候触幼,并不是通過給property加atomic關(guān)鍵字來保障安全,而是將property聲明為nonatomic(nonatomic沒有g(shù)etter置谦,setter的鎖開銷),然后自己加鎖媒峡。

如何使用哪種鎖?

iOS給代碼加鎖的方式有很多種谅阿,常用的有:

  • @synchronized(token)

  • NSLock

  • dispatch_semaphore_t

  • OSSpinLock

這幾種鎖都可以帶來原子性酬滤,性能的損耗從上至下依次更小。

我個(gè)人建議是盯串,在編寫應(yīng)用層代碼的時(shí)候,除了OSSpinLock之外体捏,哪個(gè)順手用哪個(gè)。相較于這幾個(gè)鎖的性能差異译打,代碼邏輯的正確性更為重要。而且這幾者之間的性能差異對(duì)用戶來說奏司,絕大部分時(shí)候都感知不到。

當(dāng)然我們也會(huì)遇到少數(shù)場(chǎng)景需要追求代碼的性能竿刁,比如編寫framework,或者在多線程讀寫共享數(shù)據(jù)頻繁的場(chǎng)景食拜,我們需要大致了解鎖帶來的損耗到底有多少。

官方文檔有個(gè)數(shù)據(jù)负甸,使用Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running OS X v10.5測(cè)試,獲取mutex有大概0.2ms的損耗呻待,我們可以認(rèn)為鎖帶來的損耗大致在ms級(jí)別。

Atomic Operations

其實(shí)除了各種鎖之外奏篙,iOS上還有另一種辦法來獲取原子性,使用Atomic Operations秘通,相比鎖的損耗要小一個(gè)數(shù)量級(jí)左右,在一些追求高性能的第三方Framework代碼里可以看到這些Atomic Operations的使用充易。這些atomic operation可以在/usr/include/libkern/OSAtomic.h中查到:

[圖片上傳失敗...(image-4176fd-1604474980222)]

比如

_intA ++;

是非原子性的荸型。

OSAtomicIncrement32(&(_intA));

是原子性的,多線程安全的瑞妇。

Atomic Operation只能應(yīng)用于32位或者64位的數(shù)據(jù)類型,在多線程使用NSString或者NSArray這類對(duì)象的場(chǎng)景辕狰,還是得使用鎖。

大部分的Atomic Operation都有OSAtomicXXX蔓倍,OSAtomicXXXBarrier兩個(gè)版本,Barrier就是前面提到的memory barrier偶翅,在多線程多個(gè)變量之間存在依賴的時(shí)候使用Barrier的版本,能夠保證正確的依賴順序母剥。

對(duì)于平時(shí)編寫應(yīng)用層多線程安全代碼,我還是建議大家多使用@synchronized环疼,NSLock,或者dispatch_semaphore_t炫隶,多線程安全比多線程性能更重要,應(yīng)該在前者得到充分保證伪阶,猶有余力的時(shí)候再去追求后者煞檩。

盡量避免多線程的設(shè)計(jì)

無論我們寫過多少代碼望门,都必須要承認(rèn)多線程安全是個(gè)復(fù)雜的問題锰霜,作為程序員我們應(yīng)該盡可能的避免多線程的設(shè)計(jì),而不是去追求高明的使用鎖的技能癣缅。

后面我會(huì)寫一篇文章,介紹函數(shù)式編程及其核心思想友存,即使我們使用非函數(shù)式的編程語言,比如Objective C屡立,也能極大的幫助我們避免多線程安全的問題。

總結(jié)

iOS下多線程不安全的分析至此結(jié)束了膨俐,如何編寫多線程安全的代碼,說到底還是在于對(duì)memory layout和原子性的理解敛摘,也希望這篇文章將atomic和nonatomic的真正區(qū)別解釋清楚了:)。

歡迎關(guān)注公眾號(hào):MrPeakTech

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兄淫,一起剝皮案震驚了整個(gè)濱河市蔓姚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坡脐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挨措,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浅役,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門惧盹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乳幸,“玉大人粹断,你說我怎么就攤上這事∑柯瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵养筒,是天一觀的道長(zhǎng)端姚。 經(jīng)常有香客問我,道長(zhǎng)渐裸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任尚氛,我火速辦了婚禮,結(jié)果婚禮上怠褐,老公的妹妹穿的比我還像新娘。我一直安慰自己奈懒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布磷杏。 她就那樣靜靜地躺著捏卓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怠晴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天稿械,我揣著相機(jī)與錄音,去河邊找鬼美莫。 笑死页眯,一個(gè)胖子當(dāng)著我的面吹牛厢呵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播襟铭,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼道批!你這毒婦竟也來了错英?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤茅逮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后判哥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挺身,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年锌仅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片热芹。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖伊脓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情报腔,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布纤房,位于F島的核電站,受9級(jí)特大地震影響帆卓,放射性物質(zhì)發(fā)生泄漏巨朦。R本人自食惡果不足惜剑令,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棚蓄。 院中可真熱鬧,春花似錦梭依、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽河闰。三九已至,卻和暖如春姜性,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背部念。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儡炼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓射赛,卻偏偏與公主長(zhǎng)得像奶是,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子聂沙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355