Go-defer源碼解析

前言

defer這個(gè)關(guān)鍵字在開(kāi)發(fā)過(guò)程中上場(chǎng)率可不低慨代,初學(xué)者只會(huì)知道在當(dāng)前函數(shù)中聲明一個(gè)defer函數(shù)豹绪,那么會(huì)在當(dāng)前函數(shù)return時(shí)再去執(zhí)行defer定義的函數(shù),但具體原因是什么呢玫膀?假如在當(dāng)前函數(shù)中同時(shí)聲明多個(gè)defer函數(shù)尘盼,為何先聲明的后執(zhí)行呢?要想弄懂這些問(wèn)題兼吓,可以從本文中得到答案臂港。

Example

簡(jiǎn)單的看一段程序,代碼很簡(jiǎn)單视搏,咱們主要來(lái)分析一下defer具體的執(zhí)行流程审孽。一個(gè)簡(jiǎn)單的defer關(guān)鍵字在經(jīng)過(guò)編譯之后究竟經(jīng)過(guò)了哪些方法的調(diào)用。

func main() {
    f()
}

func f() {
    defer sum(1, 2)
}

func sum(a, b int) int {
    return a + b
}

咱們看一下匯編指令浑娜,重點(diǎn)部分都加有注釋佑力。(對(duì)函數(shù)調(diào)用過(guò)程這一塊不太了解的可以去看一下這篇文章

(gdb) disass main.f
Dump of assembler code for function main.f:                                
   0x000000000044f960 <0>:      mov    %fs:0xfffffffffffffff8,%rcx     
   0x000000000044f969 <+9>:     cmp    0x10(%rcx),%rsp                             
   0x000000000044f96d <+13>:    jbe    0x44f9cb <main.f+107>                       
   0x000000000044f96f <+15>:    sub    $0x30,%rsp # 分配函數(shù)f的棧空間
   0x000000000044f973 <+19>:    mov    %rbp,0x28(%rsp) # 保存main函數(shù)的rbp到0x28(%rsp)中
   0x000000000044f978 <+24>:    lea    0x28(%rsp),%rbp # 將0x28(%rsp)位置作為函數(shù)f的rbp
   0x000000000044f97d <+29>:    movl   $0x18,(%rsp)  # rsp(0)位置為24筋遭,代表參數(shù)長(zhǎng)度     
   0x000000000044f984 <+36>:    lea    0x23c85(%rip),%rax   # 0x473610 這里是sum函數(shù)的地址   
   0x000000000044f98b <+43>:    mov    %rax,0x8(%rsp)  # rsp(8)位置為sum函數(shù)的地址     
   0x000000000044f990 <+48>:    movq   $0x1,0x10(%rsp)  # rsp(16)位置為第一個(gè)參數(shù)值1
   0x000000000044f999 <+57>:    movq   $0x2,0x18(%rsp) # rsp(24)位置為第二個(gè)參數(shù)值2
   0x000000000044f9a2 <+66>:    callq  0x4221f0 <runtime.deferproc># 關(guān)注點(diǎn)1 
   # deferproc后自動(dòng)插入的一條指令打颤。正常情況下eax寄存器中返回的值是0,執(zhí)行接下來(lái)的業(yè)務(wù)邏輯漓滔。
   # 但是當(dāng)業(yè)務(wù)邏輯panic后编饺,并且有recover的情況下,eax寄存器中會(huì)被填入1响驴,
   # 則經(jīng)過(guò)該指令對(duì)比后直接跳轉(zhuǎn)到0x44f9bb處的deferreturn
   0x000000000044f9a7 <+71>:    test   %eax,%eax  
   0x000000000044f9a9 <+73>:    jne    0x44f9bb <main.f+91>                         
   0x000000000044f9ab <+75>:    nop                                                 
   0x000000000044f9ac <+76>:    callq  0x422a80 <runtime.deferreturn> # 關(guān)注點(diǎn)2
   0x000000000044f9b1 <+81>:    mov    0x28(%rsp),%rbp # 恢復(fù)成main函數(shù)的rbp       
   0x000000000044f9b6 <+86>:    add    $0x30,%rsp   # 將椡盖遥空間還原
   0x000000000044f9ba <+90>:    retq    
   0x000000000044f9bb <+91>:    nop
   0x000000000044f9bc <+92>:    callq  0x422a80 <runtime.deferreturn>
   0x000000000044f9c1 <+97>:    mov    0x28(%rsp),%rbp
   0x000000000044f9c6 <+102>:   add    $0x30,%rsp
   0x000000000044f9ca <+106>:   retq   
   0x000000000044f9cb <+107>:   callq  0x447840 <runtime.morestack_noctxt>
   0x000000000044f9d0 <+112>:   jmp    0x44f960 <main.f>

上面有兩個(gè)關(guān)注點(diǎn),runtime.deferprocruntime.deferreturn這兩個(gè)方法豁鲤。在了解這兩個(gè)方法之前秽誊,先看一下defer的結(jié)構(gòu)。

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer.
type _defer struct {
    siz     int32 // 參數(shù)的長(zhǎng)度琳骡,函數(shù)fn的參數(shù)長(zhǎng)度
    started bool
    sp      uintptr // sp at time of defer
    pc      uintptr // defer語(yǔ)句下一條語(yǔ)句的地址
    fn      *funcval // 按上面的例子是&funcval{fn:&sum}
    _panic  *_panic // panic that is running defer
    link    *_defer // 同一個(gè)goroutine所有被延遲執(zhí)行的函數(shù)通過(guò)該成員鏈在一起形成一個(gè)鏈表
}

deferproc

// siz=fn.fn的參數(shù)長(zhǎng)度
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
    if getg().m.curg != getg() {
        // go code on the system stack can't defer
        throw("defer on system stack")
    }

    // 獲取調(diào)用者的sp值(棧頂值)
    sp := getcallersp()
    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
    // 獲取調(diào)用者的pc
    callerpc := getcallerpc()

    // 從緩沖區(qū)獲取或者重新創(chuàng)建一個(gè)
    d := newdefer(siz)
    if d._panic != nil {
        throw("deferproc: d.panic != nil after newdefer")
    }
    d.fn = fn
    d.pc = callerpc // 在panic-recover后锅论,用來(lái)跳轉(zhuǎn)的指令
    d.sp = sp
    switch siz {
    case 0:
        // 表示參數(shù)長(zhǎng)度是0,無(wú)需進(jìn)行任何操作
    case sys.PtrSize:
        // 表示是指針參數(shù)楣号,直接拷貝地址值即可
        *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
    default:
        // 相當(dāng)于復(fù)制值到defer之后
        memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
    }

    // 正常情況下是返回0最易,然后執(zhí)行defer后面的邏輯,最后在f中執(zhí)行return時(shí)調(diào)用deferreturn
    // 異常情況下(panic-recover)返回1竖席,直接執(zhí)行deferreturn
    return0()
}

首先耘纱,deferproc需要兩個(gè)參數(shù),第一個(gè)是defer函數(shù)的參數(shù)的長(zhǎng)度(以字節(jié)為單位的)毕荐,第二個(gè)參數(shù) funcval 是一個(gè)變長(zhǎng)結(jié)構(gòu)體束析。如下所示:

type funcval struct {
     fn uintptr 
    // variable-size, fn-specific data here
}

fn的參數(shù)部分緊隨著fn,按本文的例子憎亚,這里的fn=&sum员寇,參數(shù)a,b以及返回參數(shù)緊跟著fn排列弄慰。由于go中函數(shù)調(diào)用參數(shù)通過(guò)棧來(lái)傳遞,所以此時(shí)堆棧結(jié)構(gòu)是:

deferproc

在函數(shù)deferproc中蝶锋,

  1. 會(huì)先通過(guò)newdefer(siz)獲取一個(gè)defer結(jié)構(gòu)體對(duì)應(yīng)的對(duì)象陆爽。具體是從緩存中獲取還是新分配一個(gè),下面會(huì)進(jìn)行詳細(xì)的解釋扳缕。
  2. 并給其sp屬性附上當(dāng)前調(diào)用者f的棧頂sp的值慌闭,后面進(jìn)行deferreturn時(shí)會(huì)通過(guò)這個(gè)值去進(jìn)行判斷要執(zhí)行的defer是否屬于當(dāng)前調(diào)用者。
  3. 然后會(huì)將參數(shù)部分拷貝到緊挨著defer對(duì)象后面的地址:deferArgs(d)=unsafe.Pointer(d)+unsafe.Sizeof(*d)躯舔。
  4. 執(zhí)行return0函數(shù)驴剔,正常情況下返回0,經(jīng)過(guò)test %eax,%eax檢測(cè)后繼續(xù)執(zhí)行業(yè)務(wù)邏輯粥庄。異常情況下會(huì)返回1丧失,并且直接跳轉(zhuǎn)到deferreturn

newdefer

//go:nosplit
func newdefer(siz int32) *_defer {
    var d *_defer
    // 計(jì)算出sc惜互,方便從p的緩存池或者sched全局緩沖池中獲取defer
    sc := deferclass(uintptr(siz))
    gp := getg()
    if sc < uintptr(len(p{}.deferpool)) {
        pp := gp.m.p.ptr()
        if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
            // 當(dāng)前p上緩存用完了布讹,則需要從全局區(qū)拷貝幾個(gè)出來(lái),
            // 直到拷貝到deferpool容量的一半
            systemstack(func() {
                // 加上鎖训堆,有可能多個(gè)p同時(shí)去拉取
                lock(&sched.deferlock)
                for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
                    d := sched.deferpool[sc]
                    sched.deferpool[sc] = d.link
                    d.link = nil
                    pp.deferpool[sc] = append(pp.deferpool[sc], d)
                }
                unlock(&sched.deferlock)
            })
        }
        if n := len(pp.deferpool[sc]); n > 0 {
            d = pp.deferpool[sc][n-1]
            pp.deferpool[sc][n-1] = nil
            pp.deferpool[sc] = pp.deferpool[sc][:n-1]
        }
    }
    if d == nil {
        // Allocate new defer+args.
        // 當(dāng)前p和全局緩沖池中都沒(méi)有或者需要的參數(shù)過(guò)長(zhǎng)描验,則需要新分配一個(gè)
        systemstack(func() {
            total := roundupsize(totaldefersize(uintptr(siz)))
            d = (*_defer)(mallocgc(total, deferType, true))
        })
        if debugCachedWork {
            // Duplicate the tail below so if there's a
            // crash in checkPut we can tell if d was just
            // allocated or came from the pool.
            d.siz = siz
            d.link = gp._defer
            gp._defer = d
            return d
        }
    }
    d.siz = siz
    // 與之前綁定在g上的defer形成一個(gè)鏈表
    // 例如之前g上綁定1號(hào)defer,新加進(jìn)來(lái)一個(gè)2號(hào)defer
    // 那么現(xiàn)在g上defer的順序是 2-->1,這也變向的驗(yàn)證了先聲明的defer后執(zhí)行
    d.link = gp._defer
    gp._defer = d
    return d
}

分配defer的流程大概分為三步走:

  1. 由于緩沖區(qū)中是根據(jù)參數(shù)的長(zhǎng)度進(jìn)行不同級(jí)別的defer來(lái)緩存的,所以得先計(jì)算出相應(yīng)的sc值坑鱼。
  2. 判斷sc的值是否在緩沖區(qū)允許的范圍內(nèi)挠乳。
    • 在范圍內(nèi),即sc<5姑躲,則先檢查當(dāng)前p的緩沖區(qū)中是否還有可用的defer
      • 沒(méi)有可用盟蚣,則去全局緩沖區(qū)sched中批量拉取直至達(dá)到當(dāng)前p緩沖區(qū)容量的一半黍析。再?gòu)漠?dāng)前p中去獲取。
      • 若有可用屎开,則直接取出當(dāng)前p緩沖區(qū)對(duì)應(yīng)尺寸的[]defer最后一個(gè)元素即可阐枣。
  3. 無(wú)法從緩沖區(qū)中獲取或者是參數(shù)過(guò)大(sc不在緩沖區(qū)范圍內(nèi)),則直接新分配一個(gè)奄抽。
  4. 將獲取的defer綁定到當(dāng)前goroutine上蔼两,并與之前綁定的defer形成鏈表。

deferreturn

func deferreturn(arg0 uintptr) {
    gp := getg()
    // 獲取g上綁定的第一個(gè)defer
    d := gp._defer
    if d == nil {
        // 由于是遞歸調(diào)用逞度,這里是一個(gè)循環(huán)終止條件额划,d上已經(jīng)沒(méi)有綁定的defer了
        return
    }
    // 獲取當(dāng)前調(diào)用者的sp
    sp := getcallersp()
    if d.sp != sp {
        // 判斷當(dāng)前調(diào)用者棧是否和defer中保存的一致
        // 舉個(gè)例子,a()中聲明一個(gè)defer1档泽,并調(diào)用b(),b中也聲明一個(gè)defer2
        // 然后defer1和defer2都綁定在同一個(gè)g上
        // 那么在b()執(zhí)行return時(shí)俊戳,只會(huì)執(zhí)行defer2揖赴,因?yàn)閐efer2上綁定的才是b()的sp
        return
    }
    // Moving arguments around.
    //
    // Everything called after this point must be recursively
    // nosplit because the garbage collector won't know the form
    // of the arguments until the jmpdefer can flip the PC over to
    // fn.
    switch d.siz {
    case 0:
        // Do nothing.
    case sys.PtrSize:
        *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
    default:
        memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
    }
    fn := d.fn
    d.fn = nil
    // g中的defer指向下一個(gè)defer
    gp._defer = d.link
    // 進(jìn)行釋放,歸還到相應(yīng)的緩沖區(qū)或者讓gc回收
    freedefer(d)
    // 執(zhí)行defer中綁定的func
    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}
  1. 判斷當(dāng)前goroutine上是否還有綁定的defer抑胎,若沒(méi)有燥滑,直接return。
  2. 獲取goroutine綁定的defer鏈表頭部的defer阿逃。
  3. 判斷當(dāng)前defer中存儲(chǔ)的sp是否和調(diào)用者的sp一致铭拧,若不一致,也直接return恃锉,證明當(dāng)前defer不是在此調(diào)用函數(shù)中聲明的搀菩。
  4. 進(jìn)行參數(shù)的拷貝。
  5. 釋放當(dāng)前要執(zhí)行fn關(guān)聯(lián)的defer淡喜。
  6. 執(zhí)行jmpdefer函數(shù)秕磷,這里會(huì)執(zhí)行完fn的邏輯后遞歸調(diào)用deferreturn函數(shù)。

接著看一下jmpdefer函數(shù)炼团,咱們拆開(kāi)一句句的去看澎嚣。

runtime/asm_amd64.s:566

// func jmpdefer(fv *funcval, argp uintptr)
// argp is a caller SP.
// called from deferreturn.
// 1. pop the caller
// 2. sub 5 bytes from the callers return
// 3. jmp to the argument
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
    MOVQ    fv+0(FP), DX    // fn
    MOVQ    argp+8(FP), BX  // caller sp
    LEAQ    -8(BX), SP  // caller sp after CALL
    MOVQ    -8(SP), BP  // restore BP as if deferreturn returned (harmless if framepointers not in use)
    SUBQ    $5, (SP)    // return to CALL again
    MOVQ    0(DX), BX
    JMP BX  // but first run the deferred function

jmpdefer

在執(zhí)行deferreturn調(diào)用時(shí),棧幀內(nèi)部結(jié)構(gòu)大概如上圖所示瘟芝,咱們簡(jiǎn)單的梳理一下易桃。deferreturn只有一個(gè)參數(shù)arg0,所以在棧中的具體位置肯定是靠近其返回地址的锌俱。返回地址對(duì)應(yīng)的sp實(shí)際上是函數(shù)f的sp晤郑。

這里咱們將上面的匯編代碼拆解進(jìn)行一一分析:

MOVQ    fv+0(FP), DX   

fv中的第一個(gè)參數(shù)fn放到DX寄存器中。

MOVQ    argp+8(FP), BX

argp的地址放到BX寄存器中贸宏,實(shí)際上就是上圖中的&arg0造寝。

LEAQ    -8(BX), SP

將BX地址減去8,并將對(duì)應(yīng)的地址放到寄存器SP中吭练。實(shí)際上&arg0地址減去8指向的是deferreturn的返回地址诫龙,也就是callq 0x422a80 <runtime.deferreturn>的下一條指令mov 0x28(%rsp),%rbp,所以這里實(shí)際上將SP寄存器指向函數(shù)f的棧頂鲫咽。

MOVQ    -8(SP), BP

SP-8的位置恰好存放函數(shù)f的bp签赃,這里將其值存放到BP寄存器中。經(jīng)過(guò)了上面四句指令分尸,已經(jīng)成功將函數(shù)棧從deferreturn切換到了f中锦聊。

SUBQ    $5, (SP)

將SP向上移動(dòng)5位,一般人可能會(huì)在這里有疑惑箩绍,咱們先看一張圖片

jmpdefer2

咱們上面有說(shuō)到孔庭,sp指向的位置是deferreturn的返回地址,也就是callq 0x422a80 <runtime.deferreturn>的下一條指令mov 0x28(%rsp),%rbp伶选。很神奇的就是SP-5后史飞,咱們可以發(fā)現(xiàn)SP又指向了callq 0x422a80 <runtime.deferreturn>的位置尖昏,相當(dāng)于該位置成為了棧頂。這樣在執(zhí)行完函數(shù)fn之后构资,又會(huì)繼續(xù)執(zhí)行deferreturn函數(shù)抽诉,相當(dāng)于一個(gè)遞歸調(diào)用。

MOVQ    0(DX), BX #將fn地址放到BX寄存器中
JMP BX

跳轉(zhuǎn)到指定的fn函數(shù)去執(zhí)行相關(guān)邏輯吐绵,執(zhí)行完成后跳轉(zhuǎn)到deferreturn函數(shù)迹淌。

freedefer

//go:nosplit
func freedefer(d *_defer) {
    if d._panic != nil {
        freedeferpanic()
    }
    if d.fn != nil {
        freedeferfn()
    }
    sc := deferclass(uintptr(d.siz))
    if sc >= uintptr(len(p{}.deferpool)) {
        // 參數(shù)過(guò)大的不進(jìn)行緩存,等gc進(jìn)行回收
        return
    }
    pp := getg().m.p.ptr()
    if len(pp.deferpool[sc]) == cap(pp.deferpool[sc]) {
        // 當(dāng)前p中緩沖區(qū)已滿己单,則遷移一半的defer到全局緩沖區(qū)
        systemstack(func() {
            var first, last *_defer
            for len(pp.deferpool[sc]) > cap(pp.deferpool[sc])/2 {
                n := len(pp.deferpool[sc])
                d := pp.deferpool[sc][n-1]
                pp.deferpool[sc][n-1] = nil
                pp.deferpool[sc] = pp.deferpool[sc][:n-1]
                if first == nil {
                    first = d
                } else {
                    last.link = d
                }
                last = d
            }
            lock(&sched.deferlock)
            last.link = sched.deferpool[sc]
            sched.deferpool[sc] = first
            unlock(&sched.deferlock)
        })
    }

    // These lines used to be simply `*d = _defer{}` but that
    // started causing a nosplit stack overflow via typedmemmove.
    d.siz = 0
    d.started = false
    d.sp = 0
    d.pc = 0
    // d._panic and d.fn must be nil already.
    // If not, we would have called freedeferpanic or freedeferfn above,
    // both of which throw.
    d.link = nil
    // 緩存到當(dāng)前p的緩沖區(qū)
    pp.deferpool[sc] = append(pp.deferpool[sc], d)
}

釋放已經(jīng)用過(guò)的defer

  1. 首先判斷參數(shù)長(zhǎng)度唉窃,參數(shù)長(zhǎng)度過(guò)長(zhǎng)的直接讓gc進(jìn)行回收即可,無(wú)需歸還到緩沖區(qū)中纹笼。
  2. 若當(dāng)前p的緩沖區(qū)已經(jīng)滿了纹份,則需要進(jìn)行遷移操作,這里會(huì)將當(dāng)前p容量一半的defer歸還到全局緩沖區(qū)廷痘,供給其他的p使用蔓涧。操作的時(shí)候需要加上鎖,防止多個(gè)p出現(xiàn)并發(fā)操作笋额。
  3. 將其屬性置空元暴,并追加到pp.deferpool[sc]數(shù)組中。

總結(jié)

本文主要涉及到defer這個(gè)關(guān)鍵字在編譯之后究竟是怎樣嵌入我們的代碼的兄猩,以及defer后的函數(shù)是何時(shí)調(diào)用的茉盏,具體流程會(huì)先調(diào)用deferproc在goroutine上綁定defer鏈表,然后執(zhí)行deferreturn時(shí)依次遍歷鏈表執(zhí)行defer中的函數(shù)枢冤,正向插入鸠姨,反向遍歷,這樣也能看出先定義的defer后執(zhí)行淹真。當(dāng)然還有部分點(diǎn)未做涉及享怀,例如panic后當(dāng)前goroutinedefer鏈表中函數(shù)的調(diào)用,以及recover是如何實(shí)現(xiàn)的趟咆。下一篇文章會(huì)對(duì)這一塊進(jìn)行分析。

參考文章

深入理解defer(下)defer實(shí)現(xiàn)機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梅屉,一起剝皮案震驚了整個(gè)濱河市值纱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坯汤,老刑警劉巖虐唠,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惰聂,居然都是意外死亡疆偿,警方通過(guò)查閱死者的電腦和手機(jī)咱筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)杆故,“玉大人迅箩,你說(shuō)我怎么就攤上這事〈︻酰” “怎么了饲趋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撤蟆。 經(jīng)常有香客問(wèn)我奕塑,道長(zhǎng),這世上最難降的妖魔是什么家肯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任龄砰,我火速辦了婚禮,結(jié)果婚禮上讨衣,老公的妹妹穿的比我還像新娘换棚。我一直安慰自己,他們只是感情好值依,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布圃泡。 她就那樣靜靜地躺著,像睡著了一般愿险。 火紅的嫁衣襯著肌膚如雪颇蜡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天辆亏,我揣著相機(jī)與錄音风秤,去河邊找鬼。 笑死扮叨,一個(gè)胖子當(dāng)著我的面吹牛缤弦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彻磁,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼碍沐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了衷蜓?” 一聲冷哼從身側(cè)響起累提,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磁浇,沒(méi)想到半個(gè)月后斋陪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年无虚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缔赠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡友题,死狀恐怖嗤堰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咆爽,我是刑警寧澤梁棠,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站斗埂,受9級(jí)特大地震影響符糊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呛凶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一男娄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漾稀,春花似錦模闲、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至殷蛇,卻和暖如春实夹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粒梦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工亮航, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匀们。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓缴淋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親泄朴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子重抖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349