Kotlin/Native 異步并發(fā)模型(1)—— Worker 與對(duì)象子圖

Kotlin/Native 現(xiàn)狀的一些討論

Kotlin/Native 編寫的程序作為一種原生二進(jìn)制程序悼瓮,沒(méi)有強(qiáng)大的運(yùn)行時(shí)虛擬機(jī)來(lái)提供各種運(yùn)行時(shí)的保障,
因此它需要重新思考一套自己的異步并發(fā)模型。實(shí)際上 JVM 這一套機(jī)制是 C/C++
這種傳統(tǒng)命令式編程語(yǔ)言的線程同步機(jī)制的延續(xù)丙猬,但 Kotlin 在編程范式上吸收了部分函數(shù)式編程的特性氧枣,因此 Kotlin/Native
的同步方案從設(shè)計(jì)思想上向函數(shù)式編程靠攏,即對(duì)象不可變曙旭,其宗旨就是如果對(duì)象本身不可變盗舰,那就不存在線程安全的問(wèn)題。

Kotlin/Native 中桂躏,我們能實(shí)現(xiàn)的異步和并發(fā)方案有好幾種钻趋,甚是混亂。第一種方式是沼头,我們可以直接使用相關(guān)操作系統(tǒng)平臺(tái)提供的 API
來(lái)自己開(kāi)啟線程爷绘,例如在 Linux 上,我們就可以像寫 C 語(yǔ)言程序一樣进倍,自己手動(dòng)調(diào)用 pthread_create
來(lái)創(chuàng)建線程土至,但是這樣寫出來(lái)的代碼就違反了平臺(tái)通用性的原則,例如如果你要將你的程序移植到 Windows 上猾昆,那異步并發(fā)方式就得全部改用
Windows 平臺(tái)的機(jī)制陶因,可移植性太差,在編寫多平臺(tái)程序的時(shí)候這種方式就很丑陋垂蜗。

Kotlin/Native 自身提供給了我們兩套異步并發(fā)的 API楷扬,首先是協(xié)程解幽,但 Kotlin/Native 的協(xié)程與 Kotlin/JVM
的協(xié)程區(qū)別很大,Kotlin/Native 的協(xié)程是單線程的烘苹,也就是說(shuō)它只能用來(lái)執(zhí)行一些不占用 CPU 資源的并發(fā)任務(wù)躲株,例如網(wǎng)絡(luò)請(qǐng)求,如果要利用 CPU
多核的能力來(lái)進(jìn)行并行計(jì)算镣衡,Native 版的協(xié)程就失去了作用霜定,當(dāng)然,官方說(shuō)了要盡快解決這個(gè)問(wèn)題廊鸥,并且前幾天(2019 年 12
月底)我發(fā)現(xiàn)官方已經(jīng)發(fā)布了 Native 多線程版協(xié)程的預(yù)覽版本望浩,這個(gè)會(huì)在后文詳細(xì)討論。因?yàn)楫?dāng)前主分支版本的協(xié)程不能并行計(jì)算惰说,因此官方在 Kotlin/Native
誕生之初就已經(jīng)提供了另一套專門做并行任務(wù)的工具磨德,即 WorkerWorker 與 Kotlin/Native 的異步并發(fā)模型緊密相連吆视,做到了既能利用 CPU
多核能力典挑,又能保障線程安全(雖然做法很粗暴)。這篇文章我們會(huì)討論 Worker 與 Kotlin/Native 異步并發(fā)機(jī)制揩环,而協(xié)程將在下一篇討論搔弄。

Worker 初步使用

首先用 Intellij IDEA 創(chuàng)建一個(gè)基本的 Kotlin/Native 工程。我當(dāng)前電腦的操作系統(tǒng)版本是
macOS 10.15.1丰滑,因此后面的一些示例和測(cè)試方案都基于該系統(tǒng)顾犹,作為類 Unix 系統(tǒng),Linux 上的對(duì)應(yīng)行為可能也相差無(wú)幾褒墨,
但是這些示例不保證在 Windows 等系統(tǒng)上也全部可用炫刷,或行為完全一致。

先來(lái)看看 Worker 怎么用郁妈。然后我們?cè)?main 函數(shù)中編寫以下代碼:

fun main() {
    val worker = Worker.start(true, "worker1")  // 1
    worker.execute(TransferMode.SAFE, { 2 + 1 }) {
        (it + 100).toString()
    }.consume {
        println(it)
    }
}

使用 Worker.start 函數(shù)我們就可以創(chuàng)建一個(gè)新的 Worker浑玛,然后調(diào)用 Workerexecute
函數(shù)就可以在別的線程執(zhí)行任務(wù)了。這個(gè)函數(shù)接收三個(gè)參數(shù)噩咪,第一個(gè)我們先不看顾彰,第二個(gè)參數(shù),即示例中的 { 2 + 1 }
將扮演一個(gè)生產(chǎn)者的角色(為了簡(jiǎn)便胃碾,后文我們使用源碼中的命名 producer 來(lái)稱呼它)涨享,它會(huì)在外面的線程執(zhí)行,producer
的返回值將在第三個(gè)參數(shù)(也是個(gè) lambda 表達(dá)式仆百,同樣厕隧,后文我們用源碼中的命名 job 來(lái)稱呼它)中作為參數(shù)來(lái)提供。
而 job 中的代碼會(huì)在別的線程中執(zhí)行。
最后 execute 函數(shù)的返回結(jié)果是一個(gè) Future<T> 類型的對(duì)象吁讨,調(diào)用它的成員函數(shù) consume
即可獲得在 job 執(zhí)行的結(jié)果髓迎。運(yùn)行代碼驗(yàn)證一下,結(jié)果如下:

103

現(xiàn)在還要驗(yàn)證一個(gè)問(wèn)題建丧,producer 與 job 還有 consume 到底在哪個(gè)線程執(zhí)行排龄,雖然官方文檔肯定不會(huì)騙我們,但是我們自己要掌握驗(yàn)證的方法:

fun main() {
    val worker = Worker.start(true, "worker1")
    println("位置 1 的線程 id:${pthread_self()!!.rawValue.toLong()}")
    worker.execute(TransferMode.SAFE, {
        println("位置 2 的線程 id:${pthread_self()!!.rawValue.toLong()}")
        2 + 1
    }) {
        println("位置 3 的線程 id:${pthread_self()!!.rawValue.toLong()}")
        (it + 100).toString()
    }.consume {
        println("位置 4 的線程 id:${pthread_self()!!.rawValue.toLong()}")
        // println(it)
    }
}

我們?cè)?3 個(gè)位置上都使用 pthread_self() 函數(shù)來(lái)打印當(dāng)前線程 id茶鹃,輸出如下:

位置 1 的線程 id:4484095424
位置 2 的線程 id:4484095424
位置 3 的線程 id:123145437896704
位置 4 的線程 id:4484095424

果然涣雕,官方文檔誠(chéng)不欺我(手動(dòng)狗頭)艰亮。

有了直觀的認(rèn)識(shí)之后闭翩,我們會(huì)發(fā)現(xiàn) Worker 用起來(lái)和協(xié)程中的 async/await 有點(diǎn)像。但是我們發(fā)現(xiàn)它比 async/await
要麻煩迄埃,同樣疗韵,我們先不看 execute 函數(shù)的第一個(gè)參數(shù),我們可能會(huì)覺(jué)得 producer 有點(diǎn)多此一舉侄非,為什么在其他線程執(zhí)行的 job
必須使用 producer 傳遞過(guò)來(lái)的參數(shù)蕉汪,它直接捕捉上下文的變量不行嗎?為了驗(yàn)證這一點(diǎn)逞怨,于是就有了如下代碼:

fun main() {
    val worker = Worker.start(true, "worker1")
    val a = "第二個(gè)參數(shù)是干啥用的者疤?"
    worker.execute(TransferMode.SAFE, { 2 + 1 }) {
        println(a)
        (it + 100).toString()
    }.consume {
        println(it)
    }
}

重新運(yùn)行程序,直接編譯報(bào)錯(cuò):

e: kotlin.native.concurrent.Worker.execute must take an unbound, non-capturing function or lambda

為了讓信息簡(jiǎn)潔一點(diǎn)叠赦,上面復(fù)制過(guò)來(lái)的報(bào)錯(cuò)信息省略了報(bào)錯(cuò)的文件以及行數(shù)驹马。我們可以看到報(bào)錯(cuò)信息中說(shuō),在 Worker
中執(zhí)行的函數(shù)或 lambda 表達(dá)式不能有變量捕捉除秀。于是糯累,這就代表著,producer 是 job 與外界線程進(jìn)行數(shù)據(jù)傳遞的唯一入口册踩,job
無(wú)法通過(guò)變量捕捉自由訪問(wèn)外界線程的對(duì)象泳姐。這么看起來(lái) Worker 的實(shí)際太粗暴了,如果我要一次傳遞兩個(gè)對(duì)象怎么辦暂吉?用
Pair 包裝一下胖秒,那一次要傳遞三個(gè)對(duì)象呢?用 Triple慕的!四個(gè)呢阎肝?呃……F**k。

對(duì)象的傳遞

現(xiàn)在业稼,我們?cè)谥骶€程創(chuàng)建了一個(gè)對(duì)象盗痒,我們想把它傳遞到 Worker 中,由于 producer 是在外部線程中運(yùn)行的,
且對(duì)外部的對(duì)象進(jìn)行變量捕捉不會(huì)失敗俯邓,因此我們自然而然可能會(huì)寫出如下代碼骡楼。

fun main() {
    val worker = Worker.start(true, "worker1")
    val testData = TestData()
    val future = worker.execute(TransferMode.SAFE, { testData }) {
        it
    }
    future.consume { println(it.index) }
}

data class TestData(var index: Int = 0)

然后理所當(dāng)然的運(yùn)行報(bào)錯(cuò):

Uncaught Kotlin exception: kotlin.IllegalStateException: Illegal transfer state

然后我們?nèi)タ纯?execute 的第一個(gè)參數(shù) TransferMode,這是一個(gè)枚舉類型稽鞭,共有兩個(gè)枚舉值鸟整,
我們?nèi)ピ创a注釋中看看這兩個(gè)值的區(qū)別:

……

不復(fù)制粘貼了,有點(diǎn)長(zhǎng)朦蕴,大意就是:在 SAFE 模式下篮条,如果傳遞到 Worker 的對(duì)象可被別的線程或 Worker 引用到,則直接拋出異常吩抓,而在
UNSAFE 模式下涉茧,不做檢查,而是直接把對(duì)象傳遞過(guò)去疹娶,但是有可能會(huì)造成程序崩潰伴栓。接下來(lái)我們要驗(yàn)證兩個(gè)事情:

第一,當(dāng)主線程把對(duì)象傳遞給 Worker 后就不再持有對(duì)該對(duì)象的引用雨饺,SAFE 模式是否可以正常工作:

fun main() {
    val worker = Worker.start(true, "worker1")
    var testData: TestData? = TestData()
    val future = worker.execute(TransferMode.SAFE, {
        val data = testData!!
        testData = null
        data
    }) { data ->
        repeat(20000) { data.index++ }
        data
    }
    future.consume { println(it.index) }
}

data class TestData(var index: Int = 0)

程序正常打印輸出 20000钳垮。這樣來(lái)看 SAFE 模式這樣設(shè)計(jì)的確是合理的,如果主線程將對(duì)象傳遞給 Worker
之后仍然可以繼續(xù)訪問(wèn)對(duì)象额港,那就可能發(fā)生線程安全問(wèn)題饺窿,因此 SAFE 模式直接拒絕了這種事情的發(fā)生而拋出異常,但是這樣的寫法太丑陋了移斩,
如果要實(shí)現(xiàn)更優(yōu)雅的寫法肚医,唯一的辦法就是讓 testData 的引用范圍不超出 produce,也就是說(shuō)把數(shù)據(jù)產(chǎn)生的過(guò)程都寫到 produce
里面叹哭,雖然這樣也沒(méi)有那么優(yōu)雅忍宋,但是還能接受。

官方提供了一套理論來(lái)解釋上面示例程序所表現(xiàn)出來(lái)的行為:被 producer 傳遞的對(duì)象會(huì)被包裝一個(gè)叫做對(duì)象子圖(object
subgraph)的東西风罩,對(duì)象子圖生成之后糠排,原線程就不能再訪問(wèn)對(duì)象子圖,如果是在 SAFE
模式超升,就會(huì)使用圖遍歷算法檢查對(duì)象子圖的訪問(wèn)入宦。以上都是目前官方文檔的闡述,
關(guān)于 Worker 的更多資料我覺(jué)得官方在日后還會(huì)有更多補(bǔ)充室琢,等到那時(shí)再詳細(xì)分析乾闰。

再來(lái)看看 UNSAFE 模式:

fun main() {
    val worker = Worker.start(true, "worker1")
    val testData = TestData()
    val future = worker.execute(TransferMode.UNSAFE, { testData }) { data ->
        repeat(20000) { data.index++ }
        data
    }
    repeat(20000) { testData.index++ }
    future.consume { println(it.index) }
}

data class TestData(var index: Int = 0)

如果線程訪問(wèn)是安全的,應(yīng)該輸出 40000盈滴,但是你每次運(yùn)行這段代碼得到的結(jié)果都會(huì)不同涯肩,反正都小于 40000轿钠。所以,果然 UNSAFE
模式簡(jiǎn)單粗暴病苗,直接撒手不管了疗垛,我最初的預(yù)測(cè)是,當(dāng)兩個(gè)線程真正發(fā)生同一時(shí)刻訪問(wèn)同一個(gè)變量的時(shí)候會(huì)發(fā)生崩潰硫朦,
而在其他情況下贷腕,程序照常運(yùn)行,就像源碼注釋里說(shuō)的那樣咬展。但事實(shí)并非如此泽裳,所以我建議,千萬(wàn)不要靠"人"來(lái)保障線程安全破婆,在
99.99% 的情況下都應(yīng)該使用 SAFE 模式涮总,如果使用 UNSAFE 模式,風(fēng)險(xiǎn)將直接暴露出來(lái)荠割,且 Kotlin/Native
沒(méi)有線程鎖來(lái)幫你兜底妹卿。

對(duì)象子圖凍結(jié)、全局變量以及單例

上面已經(jīng)討論了很多情況蔑鹦,但是跨線程訪問(wèn)都是在函數(shù)內(nèi)部,也就是局部變量的跨線程訪問(wèn)箕宙。但如果是全局變量嚎朽、
單例這種在多個(gè)函數(shù)內(nèi)都可以訪問(wèn)的變量,情況則會(huì)有所不同柬帕。

先闡述一個(gè)對(duì)象子圖凍結(jié)的概念哟忍,對(duì)于某些變量,我們確切知道其一定不可變陷寝,那對(duì)于這種變量锅很,無(wú)論在多少個(gè)線程中同時(shí)訪問(wèn)它都是安全的,
既然如此凤跑,那 Kotlin/Native 也沒(méi)必要對(duì)這種變量在訪問(wèn)的時(shí)候做子圖校驗(yàn)爆安,對(duì)于這樣的變量,我們就可以稱其為被凍結(jié)的變量仔引,
官方文檔關(guān)于這個(gè)地方有些前后矛盾扔仓,
先說(shuō)凍結(jié)的變量只有枚舉一種,但后面又闡述了兩種變量?jī)鼋Y(jié)的情況(后文會(huì)介紹)咖耘。還有一種情況翘簇,也有可能一個(gè)變量一開(kāi)始是非凍結(jié)的,
后面又被凍結(jié)了儿倒,但是有一點(diǎn)是不變的版保,那就是已凍結(jié)的對(duì)象不可解凍。關(guān)于在多個(gè) Worker
中訪問(wèn)枚舉變量的情況這里也就不舉例了,很簡(jiǎn)單彻犁。

下面講講幾個(gè)重要的注解和幾種重要的情況

訪問(wèn)全局變量

val abc = "abc"

fun main() {
    val worker = Worker.start(true, "worker1")
    val future = worker.execute(TransferMode.UNSAFE, {}) {
        println(abc)
    }
    future.consume { println(abc) }
}

程序正常運(yùn)行蹈垢,打印輸出:

abc
abc

這很奇怪,官方文檔說(shuō)全局變量(沒(méi)有特殊標(biāo)記)
只能在主線程訪問(wèn)袖裕,但是我們明明在子線程訪問(wèn)了它曹抬,程序卻正常運(yùn)行。我們把修飾變量 abcval 改成
var 再試一試急鳄,程序果然拋出異常:IncorrectDereferenceException谤民。

那如果是非 String 的引用類型呢?

val testData = TestData()

fun main() {
    val worker = Worker.start(true, "worker1")
    val future = worker.execute(TransferMode.UNSAFE, {}) {
        println(testData)
    }
    future.consume { println(testData) }
}

class TestData

程序拋出異常:IncorrectDereferenceException疾宏,多測(cè)試幾次后张足,基本可以得出一個(gè)結(jié)論:

  • 結(jié)論 1:對(duì)于原生類型與 String 來(lái)說(shuō),如果這些變量是用 val 修飾的坎藐,則在多個(gè)線程中訪問(wèn)沒(méi)有問(wèn)題为牍,如果是 var
    修飾的變量,則會(huì)拋出異常岩馍。對(duì)于其他引用類型的全局變量(不加特殊修飾)來(lái)說(shuō)碉咆,無(wú)論用 val 還是 var
    修飾,都只能在主線程訪問(wèn)蛀恩。

這條結(jié)論是官方文檔中沒(méi)有提到的疫铜,也算是踩坑的一個(gè)收獲。

在這里有個(gè)插曲双谆,既然 val 修飾的基本類型與 String 一定是不可變的壳咕,那對(duì)于局部變量這個(gè)結(jié)論是否也成立?
我們把對(duì)象的傳遞小節(jié)中的第一個(gè)示例修改一下:

fun main() {
    val worker = Worker.start(true, "worker1")
    val testData = "abc"
    val future = worker.execute(TransferMode.SAFE, { testData }) {
        println(it)
        it
    }
    future.consume { println(it) }
}

最主要的變化就是把 testData 換成了一個(gè) String顽馋,程序正常谓厘,多測(cè)試幾次,對(duì)原生類型也是成立的寸谜,因此結(jié)論 1對(duì)局部變量也成立竟稳。
其實(shí)仔細(xì)思考一下,對(duì)于 val 修飾的原生類型與 String程帕,從邏輯上確實(shí)可以證明它們一定是不可變住练。

@ThreadLocal 與 @SharedImmutable 以及單例

修改上面的示例:

@ThreadLocal
val testData = TestData()

fun main() {
    val worker = Worker.start(true, "worker1")
    val future = worker.execute(TransferMode.UNSAFE, {}) {
        println(++testData.index)
    }
    future.consume { println(testData.index) }
}

data class TestData(var index: Int = 0)

輸出如下:

1
0

結(jié)果與官方的相同,如果全局變量使用 @ThreadLocal 修飾愁拭,則該變量在每個(gè)線程都有不同的副本讲逛,即使修改,也在線程之間不可見(jiàn)岭埠。

再修改示例盏混,僅僅把上一個(gè)示例中的 @ThreadLocal 改成 @SharedImmutable蔚鸥,然后程序拋出異常;再把 println(++testData.index)
改成 println(testData.index) 程序運(yùn)行正常许赃,根據(jù)官方的說(shuō)法 @SharedImmutable 的作用是將變量?jī)鼋Y(jié)止喷,這樣的話該變量就可以共享了,
但它畢竟只是一個(gè)注解混聊,如果你編寫了修改該變量的代碼弹谁,也只能在運(yùn)行時(shí)才能發(fā)現(xiàn)問(wèn)題。

最后看看單例:

object A {
    var index = 1
}

fun main() {
    val worker = Worker.start(true, "worker1")
    val future = worker.execute(TransferMode.UNSAFE, {}) {
        println(A.index)
    }
    future.consume { println(A.index) }
}

如果運(yùn)行程序句喜,我們就發(fā)現(xiàn) object 修飾的單例與使用 @SharedImmutable 修飾的全局變量行為是一致的预愤,不過(guò),
單例也可以使用 @ThreadLocal 來(lái)修飾咳胃,這也就不多說(shuō)了植康。

總結(jié)以及其他

如果說(shuō)還有什么是我沒(méi)有提到的,那應(yīng)該就是對(duì)象子圖分離和原始共享內(nèi)存展懈,不過(guò)這兩部分內(nèi)容主要是用于 C
程序與 Kotlin/Native 交互的情況销睁,例如將 Kotlin/Native 對(duì)象保存到 C 結(jié)構(gòu)體中,在真實(shí)的用例中存崖,我們使用 Kotlin/Native
調(diào)用 C 代碼的情況應(yīng)該占絕大多數(shù)冻记,而使用 C 調(diào)用 Kotlin/Native 應(yīng)該極少發(fā)生,因此以后有機(jī)會(huì)再探討這部分內(nèi)容金句。

開(kāi)篇講過(guò) Worker 是目前 Kotlin/Native 實(shí)現(xiàn)并行計(jì)算的主要工具檩赢,不過(guò) Native 版的協(xié)程最近也推出了多線程版本的預(yù)覽版,
關(guān)于這部分內(nèi)容將是下一篇文章要重點(diǎn)探討的违寞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市偶房,隨后出現(xiàn)的幾起案子趁曼,更是在濱河造成了極大的恐慌,老刑警劉巖棕洋,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挡闰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掰盘,警方通過(guò)查閱死者的電腦和手機(jī)摄悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)愧捕,“玉大人奢驯,你說(shuō)我怎么就攤上這事〈位妫” “怎么了瘪阁?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵撒遣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我管跺,道長(zhǎng)义黎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任豁跑,我火速辦了婚禮廉涕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艇拍。我一直安慰自己狐蜕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布淑倾。 她就那樣靜靜地躺著馏鹤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娇哆。 梳的紋絲不亂的頭發(fā)上湃累,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音碍讨,去河邊找鬼治力。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勃黍,可吹牛的內(nèi)容都是我干的宵统。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼覆获,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼马澈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起弄息,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痊班,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后摹量,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涤伐,經(jīng)...
    沈念sama閱讀 45,724評(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,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睦尽,死狀恐怖器净,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骂删,我是刑警寧澤掌动,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布四啰,位于F島的核電站,受9級(jí)特大地震影響粗恢,放射性物質(zhì)發(fā)生泄漏柑晒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一眷射、第九天 我趴在偏房一處隱蔽的房頂上張望匙赞。 院中可真熱鬧,春花似錦妖碉、人聲如沸涌庭。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坐榆。三九已至,卻和暖如春冗茸,著一層夾襖步出監(jiān)牢的瞬間席镀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工夏漱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豪诲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓挂绰,卻偏偏與公主長(zhǎng)得像屎篱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子葵蒂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355