Golang調(diào)度器和GMP模型

一、調(diào)度器的由來

調(diào)度本身是指操作系統(tǒng)中為每個任務(wù)分配其所需資源的方法忌怎。

在操作系充中杀狡,線程是任務(wù)執(zhí)行的最小單位蒙畴,是系統(tǒng)調(diào)度的基本單元。

雖然線程比進程輕量捣卤,但是在調(diào)度時也有比較大的額外開銷忍抽,每個線程都會占用幾 M 的內(nèi)存八孝,上下文切換時也會消耗幾微秒的時間董朝,這些都是高并發(fā)的阻礙。

Go 語言的誕生有一個很重要的目的就是并發(fā)編程干跛,所以開發(fā)者為Go語言設(shè)計了一個調(diào)度器來解決這些問題子姜。

Go 的調(diào)度器通過使用與 CPU 數(shù)量相等的線程減少線程頻繁切換的內(nèi)存和時間開銷,同時在每一個線程上執(zhí)行額外開銷更低的協(xié)程來降低資源占用,完成更高并發(fā)哥捕。

1.1 Go 調(diào)度器的歷史

  • 單線程調(diào)度器

最初的調(diào)度器極其簡陋牧抽,只有 40 多行代碼,程序只能存在一個活躍線程遥赚,由 G (goroutine) - M (thread)組成扬舒。

  • 多線程調(diào)度器

加入了多線程后,可以運行多線程任務(wù)了凫佛,但是存在嚴(yán)重的同步競爭問題讲坎。

  • 任務(wù)竊取調(diào)度器

引入了處理器 P (processor),構(gòu)成了 GMP 模型愧薛。P 是調(diào)度器的中心晨炕,任何線程想要運行 G ,都必須服務(wù) P 的安排毫炉,由 P 完成任務(wù)竊取瓮栗。如果 G 發(fā)生阻塞,G 并不會主動讓出線程瞄勾,導(dǎo)致其他 G 饑餓费奸。

另外,這一版的調(diào)度器的垃圾回收機制使用了比較笨重的 SWT (stop the world) 機制进陡,在回收垃圾時货邓,會暫停程序很長時間。

  • 搶占式調(diào)度器(正在使用)

搶占式調(diào)度器也發(fā)展了兩個階段:

  1. 基于協(xié)作的搶占式調(diào)度器(1.12 ~ 1.13)四濒。編譯器在函數(shù)調(diào)用時檢查當(dāng)前goroutine是否發(fā)起搶占請求换况。也有 STW 的問題。
  2. 基于信號的搶占式調(diào)度器(1.14 至今)盗蟆。垃圾回收在掃描棧時會觸發(fā)搶占調(diào)度戈二,但搶占的點不夠多,不能覆蓋全部的邊緣情況喳资。
  • 非均勻存儲訪問調(diào)度器(提案)

對運行時的各種資源進行分區(qū)觉吭,但實現(xiàn)非常復(fù)雜,只停留在理論階段仆邓。

1.2 單線程調(diào)度器

最初的調(diào)度器只包括 GM 兩種結(jié)構(gòu)鲜滩,全局只有一個線程,所以系統(tǒng)中只能一個任務(wù)一個任務(wù)地執(zhí)行节值,一旦發(fā)生阻塞徙硅,整個后續(xù)任務(wù)都無法執(zhí)行。

單進程調(diào)度器的意義就是建立了 G 搞疗、M 結(jié)構(gòu)嗓蘑,為后續(xù)的調(diào)度器發(fā)展打下了基礎(chǔ)。

1.3 多線程調(diào)度器

在 1.0 版本中,Go 更換了多線程調(diào)度器桩皿,與單線程調(diào)度器相比豌汇,可以說是質(zhì)的飛躍。

多線程調(diào)度器的整體邏輯與單線程調(diào)度器沒有太大的區(qū)別泄隔,因為引入了多線程拒贱,所以 Go 使用環(huán)境變量GOMAXPROCS幫助我們靈活控制程序中的最大處理器數(shù),即活躍線程數(shù)佛嬉。

多線程調(diào)度器的主要問題是調(diào)度時的鎖競爭會嚴(yán)重浪費資源柜思,有14%的時間浪費在此。

主要問題有:

  1. 調(diào)度器和鎖是全局資源巷燥,所有的調(diào)度狀態(tài)都是中心化存儲的赡盘,鎖競爭嚴(yán)重
  2. 線程需要經(jīng)常互相傳遞可運行的 Goroutine缰揪,引入了大量的延遲
  3. 每個線程都需要處理內(nèi)存緩存陨享,導(dǎo)致大量的內(nèi)存占用并影響數(shù)據(jù)局部性
  4. 系統(tǒng)調(diào)用頻繁阻塞和解除阻塞正在運行的線程,增加額外開銷

1.4 任務(wù)竊取調(diào)度器

基于任務(wù)竊取的Go語言調(diào)度器使用了沿用至今的 GMP 模型钝腺,主要改進了兩點:

  • 在當(dāng)前的 G - M 模型中引入了處理器 P 抛姑,增加中間層
  • 在處理器 P 的基礎(chǔ)上實現(xiàn)基于工作竊取的調(diào)度器

當(dāng)前處理器P的本地隊列中如果沒有 G ,就會觸發(fā)工作竊取艳狐,從其他的 P 的隊列中隨機獲取一些 G 定硝。調(diào)度器在調(diào)度時會將P的本地隊列的隊列頭的 G 取出來,放到 M 上執(zhí)行毫目。

任務(wù)竊取調(diào)度器

1.4.1 G

Goroutine/G 是 Go 語言調(diào)度器中待執(zhí)行的任務(wù)蔬啡,它在運行時調(diào)度器的地位與線程在操作系統(tǒng)中差不多,但它占用更小的空間镀虐,也極大地降低了上下文切換的開銷箱蟆。

G 只存在于 Go 語言的運行時,它只是 Go 語言在用戶態(tài)提供的線程刮便,作為一種粒度更細(xì)的資源調(diào)度單元空猜,能夠在高并發(fā)的場景下更高效地利用機器的資源。

1.4.2 M

M的數(shù)量最多可以創(chuàng)建 10000 個恨旱,但其中大多數(shù)都不會執(zhí)行用戶代碼而是陷入系統(tǒng)調(diào)用辈毯,最多也只有GOMAXPROCS個線程能夠正常運行。

默認(rèn)情況下搜贤,程序運行時會將GOMAXPROCS設(shè)置為當(dāng)前機器的核數(shù)谆沃。且大多數(shù)情況下,我們都會使用 Go 的默認(rèn)設(shè)置入客,也就是線程數(shù)等于 CPU 數(shù)管毙。因為默認(rèn)的設(shè)置不會頻繁地觸發(fā)操作系統(tǒng)的線程調(diào)度和上下文切換,所有的調(diào)度都發(fā)生在用戶態(tài)桌硫,由 Go 語言調(diào)度器觸發(fā)夭咬,能減少很多額外開銷。

每個M都有兩個 G 铆隘,G0curg卓舵。G0是持有調(diào)度棧的 Gcurg是當(dāng)前線程上運行的用戶 G 膀钠,這是操作系統(tǒng)唯一關(guān)心的兩個 G 掏湾。

G0深度參與 G 的調(diào)度,包括 G 的創(chuàng)建肿嘲、大內(nèi)存分配和 CGO 函數(shù)的執(zhí)行融击。

1.4.3 P

Processor/P 是 GM 的中間層,提供 G 需要的上下文環(huán)境雳窟,也負(fù)責(zé)調(diào)度 P 的等待隊列和全局等待隊列尊浪。

通過 P 的調(diào)度,每一個 M 都能夠執(zhí)行多個 G 封救。當(dāng) G 進行 I/O 操作時拇涤,P 會讓這個 G 讓出計算資源,提高 M 的利用率誉结。

調(diào)度器會在啟動時創(chuàng)建GOMAXPROCS個處理器鹅士,所以 P 的數(shù)量一定會等于GOMAXPROCS,這些 P 會綁定到不同的 M 上惩坑。

1.5 搶占式調(diào)度器

因為竊取式調(diào)度器存在饑餓和 STW 耗時過長的問題掉盅,所以后續(xù)又推出了基于協(xié)作的搶占式調(diào)度器。

1.5.1 基于協(xié)作的搶占式調(diào)度器

  • 編繹器在調(diào)用函數(shù)前插入搶占函數(shù)(檢查是否需要更多的棧)
  • 運行時會在 GC 的 STW 以舒、系統(tǒng)監(jiān)控中發(fā)現(xiàn)運行時長超過 10ms 的 G 怔接,通用一個字段StackPreempt標(biāo)記需要搶占的 G
  • 在發(fā)生函數(shù)調(diào)用時,可能會執(zhí)行編繹器插入的搶占函數(shù)稀轨,搶占函數(shù)會調(diào)用創(chuàng)建新棧函數(shù)檢查 G 的搶占字段
  • 如果 G 被標(biāo)記為可搶占扼脐,就會觸發(fā)搶占讓出當(dāng)前線程

這種方式雖然增加了運行時的復(fù)雜度,但實現(xiàn)相對簡單奋刽,沒有帶來額外開銷瓦侮,因為其是通過編繹器插入函數(shù)并通過調(diào)度函數(shù)作為入口觸發(fā)搶占,所以是協(xié)作式的搶占佣谐。

1.5.2 基于信號的搶占式調(diào)度器

協(xié)作搶占在一定程度上解決了竊取調(diào)度中的問題肚吏,但卻產(chǎn)生了嚴(yán)重的邊緣問題,非常影響開發(fā)體驗[1]狭魂。

為了改善這些問題罚攀,在 1.14 版本中實現(xiàn)了非協(xié)作式的搶占式調(diào)度党觅,為 G 增加新的狀態(tài)和字段并改變原邏輯實現(xiàn)搶占。

  • 啟動程序時斋泄,注冊信號處理函數(shù)
  • 在 GC 掃描棧時將 G 標(biāo)記為可搶占杯瞻,并調(diào)用搶占函數(shù),將 G 掛起
  • 搶占函數(shù)會向線程發(fā)送信號
  • 系統(tǒng)接到信號后會中斷正在運行的線程并執(zhí)行預(yù)先注冊的信息處理函數(shù)
  • 根據(jù)搶占信號修改當(dāng)前寄存器炫掐,在程序回到用戶態(tài)時執(zhí)行異步搶占
  • 將當(dāng)前 G 標(biāo)記為被搶占后讓當(dāng)前函數(shù)陷入休眠并讓出 M 魁莉,P 選擇其他 G 繼續(xù)執(zhí)行

二、調(diào)度器運行

2.1 調(diào)度器啟動

程序初始化時會創(chuàng)建初始線程 M_0 和主協(xié)程 G_0[2]募胃,并將 G_0 綁定到 M_0 旗唁。

M_0 是啟動程序后的編號為 0 的主線程,這個 M 對應(yīng)的實例會在全局變量runtime.m0中痹束,不需要在heap上分配检疫,M0 負(fù)責(zé)執(zhí)行初始化操作和啟動第一個 G , 在之后 M_0 就和其他的 M 一樣了祷嘶。

G_0 是每次啟動一個M都會第一個創(chuàng)建的gourtine电谣,G_0 僅用于負(fù)責(zé)調(diào)度的 GG_0 不指向任何可執(zhí)行的函數(shù), 每個 M 都會有一個自己的 G_0 抹蚀。在調(diào)度或系統(tǒng)調(diào)用時會使用 G_0 的椊宋空間。

系統(tǒng)初始化完畢后調(diào)用調(diào)度器初始化环壤,此時會將maxmcount設(shè)置為 10000晒来,這是一個 Go 程序能夠創(chuàng)建的最大線程數(shù),雖然可以創(chuàng)建 10000 個線程郑现,但是可以同時運行的 M 還中由GOMAXPROCS變量控制湃崩。

從環(huán)境變量中獲取了GOMAXPROCS后就會更新程序中 P 的數(shù)量,這時整個程序不會執(zhí)行任何用戶的 G 接箫,P 也會進入鎖定狀態(tài)攒读。

  • P 全局列隊長度如果小于GOMAXPROCS就擴容
  • 遍歷 P 隊列,為隊列中為nil的位置使用new方法創(chuàng)建新的 P 辛友,并調(diào)用 P 的初始化函數(shù)
  • 通過指針將 M_0 和隊列中的第一個 P 綁定到一起
  • 釋放不再使用的 P
  • 如果全局隊列長度不等于GOMAXPROCS薄扁,則截斷全局隊列使其相等[3]
  • 之前只判斷全局隊列小于GOMAXPROCS的情況,所以這里的不相等废累,就是大于等于GOMAXPROCS
  • 將全局隊列中除第一個 P 之外的所有 P 設(shè)置為空閑狀態(tài)并加入到全局空閑隊列中

之后調(diào)度器會完成相應(yīng)數(shù)量的 P 的啟動邓梅,等待用戶創(chuàng)建新的 G 并為 G 調(diào)度處理器資源。

2.2 創(chuàng)建 G

想在啟動一個新的 G 來執(zhí)行任務(wù)邑滨,需要使用 Go 語言的go關(guān)鍵字日缨。

編繹器會根據(jù)go后的函數(shù)和當(dāng)前調(diào)用程序創(chuàng)建新的 G 結(jié)構(gòu)體并加入 P 的本地運行隊列或者全局運行隊列中恩急。

當(dāng)本地運行隊列滿時眷昆,會將本地隊列中的一部分 G 和待加入的 G 添加到全局運行隊列中锁右。

P 的本地運行隊列最多可以存儲256個 G 上荡。

2.3 調(diào)度循環(huán)

調(diào)度器啟動后,初始化 G_0 的棧毅待,然后初始化線程并進入調(diào)度循環(huán)尚卫。

2.3.1 獲取 G

  • 為了保證公平,當(dāng)全局運行隊列中有待執(zhí)行的 G 時恩静,會有一定幾率從全局隊列中獲取 G 執(zhí)行
  • 主要還是從 P 的本地運行隊列中獲取 G
  • 如果前兩種方法都沒有獲取到 G 焕毫,會通過下面三個方式進行阻塞查找 G
    1. 從本地運行隊列蹲坷、全局運行隊列中查找
    2. 從網(wǎng)絡(luò)輪詢中查找是否有 G 等待執(zhí)行
    3. 嘗試從其他隨機的的 P 中竊取 G 驶乾,還可能竊取其他 P 的計時器

所以當(dāng)前函數(shù)一定會返回一個可執(zhí)行的 G ,如果實再獲取不到循签,就會阻塞等待级乐。

2.3.2 執(zhí)行 G

G_0 將獲取到的 G 調(diào)度到當(dāng)前 M 上執(zhí)行。

當(dāng)前的 G 執(zhí)行完畢后县匠,G_0 會重新執(zhí)行調(diào)度函數(shù)觸發(fā)新一輪的調(diào)度风科。

Go 語言的運行時調(diào)度是一個循環(huán)的過程,永不返回乞旦。

上面是 G 正常執(zhí)行的調(diào)度過程贼穆,但實際上,G 也可能不正常執(zhí)行兰粉,比如阻塞住故痊,此時調(diào)度器會斷開當(dāng)前 MP 的關(guān)系,創(chuàng)建或從休眠隊列中獲取一個新的 M 重新與 P 組合玖姑。原 MG 會有一個超時時間愕秫,如果此期間沒能執(zhí)行完成,將會釋放 M 焰络,M 或休眠戴甩,或獲取一個空閑的 P 繼續(xù)工作,繼續(xù)開始新的調(diào)度過程闪彼。

2.4 線程管理

Go 語言的運行時會通過調(diào)度器改變線程的所有權(quán)甜孤,也提供了 runtime.LockOSThreadruntime.UnlockOSThread 讓我們有能力綁定 Goroutine 和線程完成一些比較特殊的操作。

Goroutine 應(yīng)該在調(diào)用操作系統(tǒng)服務(wù)或者依賴線程狀態(tài)的非 Go 語言庫時調(diào)用 runtime.LockOSThread 函數(shù)畏腕,例如:C 語言圖形庫等课蔬。

當(dāng) Goroutine 執(zhí)行完特殊操作后,會分離 Goroutine 和線程郊尝。

在多數(shù)情況下二跋,都用不到這一對函數(shù)。

2.4.1 線程生命周期

Go 語言的運行時通過 M 執(zhí)行 P流昏,如果沒有空閑 M 就會創(chuàng)建新的 M 扎即。

新創(chuàng)建線程只有主動調(diào)用退出命令或啟動函數(shù)runtime.mstart返回時主動退出吞获。

由此完成線程從創(chuàng)建到銷毀的整個閉環(huán)。

2.4.2 自旋線程

當(dāng) GMP 組合 P 的本地隊列中沒有 G 時谚鄙,M 就是自旋線程各拷,自旋線程就會如果[2.3.1](#2.3.1 獲取 G)中所說一直阻塞查找 G

為什么要有自旋線程闷营?

自旋本質(zhì)是保持線程運行烤黍,銷毀再創(chuàng)建一個新的線程要消耗大量的CPU資源和時間。

如果創(chuàng)建了一個新的 G 傻盟,立即能被自旋線程捕獲速蕊,而如果新建線程則不能保持即時性,也就降低了效率娘赴。

但自旋線程數(shù)量也不宜過多规哲,過多的自旋線程同樣也是浪費CPU資源,所以系統(tǒng)中最多有GOMAXPROCS個自旋線程诽表,通常數(shù)量有GOMAXPROCS - 正在運行的非自旋線程數(shù)量唉锌,多余的線程會休眠或被銷毀。

三竿奏、讓GMP可視化

3.1 go tool

go tool trace 記錄了運行時的信息袄简,能提供可視化的 web 頁面。

測試代碼:

// trace.go
package main

import (
    "os"
    "fmt"
    "runtime/trace"
)

func main() {
    //創(chuàng)建trace文件
    f, err := os.Create("trace.out")
    if err != nil {
        panic(err)
    }

    defer f.Close()

    //啟動trace goroutine
    err = trace.Start(f)
    if err != nil {
        panic(err)
    }
    defer trace.Stop()

    //main
    fmt.Println("Hello World")
}

運行程序:

$ go run trace.go

會得到一個trace.out文件泛啸,然后可以用 go 內(nèi)置的 trace 打開這個文件:

$ go tool trace -http=0.0.0.0:9999 trace.out
2021/03/28 17:29:24 Parsing trace...
2021/03/28 17:29:24 Splitting trace...
2021/03/28 17:29:24 Opening browser. Trace viewer is listening on http://[::]:9999

訪問http://127.0.0.1:9999/trace或你指定的 host:port 可以查看可視化的調(diào)度過程绿语。

截屏2021-03-29 18.28.51

3.1.1 G 信息

點擊 Goroutines 對應(yīng)的那一條藍(lán)色矩形,下面的窗口會輸入一些詳細(xì)信息

截屏2021-03-29 18.35.54

程序運行過程中一共創(chuàng)建了兩個 G 平痰,其中一個是必須創(chuàng)建的 G_0 汞舱。

所以 G_1 才是 main goroutine ,從可運行狀態(tài)變?yōu)檎谶\行宗雇,然后程序結(jié)束昂芜。

3.1.2 M 信息

點擊 Thread 對應(yīng)的紫色矩形:

截屏2021-03-29 18.41.36

一共有兩個 M ,其中一個是 M_0 赔蒲,程序初始化時創(chuàng)建泌神。

3.1.3 P 信息

G_1 中調(diào)用的main.main,創(chuàng)建了本次使用的trace G G_2 舞虱,G_1 運行在 P_1 上欢际, G_2 運行在 P_0 上。

3.2 Debug

//trace2.go
package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 5; i++ {
        time.Sleep(time.Second)
        fmt.Println("Hello World")
    }
}

需要先編繹:

$ go build trace2.go

運行:

$ GODEBUG=schedtrace=1000 ./trace 
SCHED 0ms: gomaxprocs=2 idleprocs=0 threads=5 spinningthreads=1 idlethreads=1 runqueue=0 [0 0]
Hello World
SCHED 1009ms: gomaxprocs=2 idleprocs=2 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0]
Hello World
SCHED 2016ms: gomaxprocs=2 idleprocs=2 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0]
Hello World
SCHED 3022ms: gomaxprocs=2 idleprocs=2 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0]
Hello World
SCHED 4030ms: gomaxprocs=2 idleprocs=2 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0]
Hello World

關(guān)鍵字說明:

  • SCHED 調(diào)度器的縮寫矾兜,標(biāo)志本行輸入是 Goroutine 調(diào)度器的輸出
  • 0ms 從程序啟動到輸出這行日志經(jīng)過的時間
  • gomaxprocs 最大活躍線程/ P 的數(shù)量损趋,與CPU數(shù)量一致
  • idleprocs 空閑 P 數(shù)量
  • threads 系統(tǒng)線程數(shù)量
  • spinningthreads 自旋線程數(shù)量
  • idlethreads 空閑線程數(shù)量
  • runqueue 調(diào)度器全局隊列中 G 的數(shù)量
  • [0 0] 分別為2個 P 的本地隊列中 G 的數(shù)量

注釋:


  1. https://github.com/golang/go/issues/24543 ?

  2. https://github.com/golang/go/blob/master/src/runtime/proc.go#L84 ?

  3. https://github.com/golang/go/blob/master/src/runtime/proc.go#L4805 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市椅寺,隨后出現(xiàn)的幾起案子浑槽,更是在濱河造成了極大的恐慌蒋失,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐玻,死亡現(xiàn)場離奇詭異篙挽,居然都是意外死亡,警方通過查閱死者的電腦和手機镊靴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門铣卡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偏竟,你說我怎么就攤上這事煮落。” “怎么了苫耸?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵州邢,是天一觀的道長儡陨。 經(jīng)常有香客問我褪子,道長,這世上最難降的妖魔是什么骗村? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任嫌褪,我火速辦了婚禮,結(jié)果婚禮上胚股,老公的妹妹穿的比我還像新娘笼痛。我一直安慰自己,他們只是感情好琅拌,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布缨伊。 她就那樣靜靜地躺著,像睡著了一般进宝。 火紅的嫁衣襯著肌膚如雪刻坊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天党晋,我揣著相機與錄音谭胚,去河邊找鬼。 笑死未玻,一個胖子當(dāng)著我的面吹牛灾而,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扳剿,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼旁趟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庇绽?” 一聲冷哼從身側(cè)響起锡搜,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤癣猾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后余爆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纷宇,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年蛾方,在試婚紗的時候發(fā)現(xiàn)自己被綠了像捶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡桩砰,死狀恐怖拓春,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亚隅,我是刑警寧澤硼莽,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站煮纵,受9級特大地震影響懂鸵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜行疏,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一匆光、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酿联,春花似錦终息、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至喳张,卻和暖如春续镇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹲姐。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工磨取, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柴墩。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓忙厌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親江咳。 傳聞我的和親對象是個殘疾皇子逢净,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

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