GCD 細細的讀

目錄

<a name="前言"></a>前言

在日常app開發中多線程的使用是難免的,既然躲不過,干嘛不好好的享受? 本文將對GCD進行全面的解析。

<a name="為什么選擇GCD?"></a>為什么選擇GCD?

它是蘋果在 iOS 4 時推出的,為多核的并行運算提出的, 以c語言編寫的解決方案。高效,自動管理是它的優點。

<a name="串行隊列、并行隊列、同步、異步"></a>串行隊列、并行隊列、同步、異步

串行隊列:放到串行隊列的任務,GCD 會FIFO(先進先出)地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。

并行隊列

  • 為異步執行時:放到并行隊列的任務,GCD 也會FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務都是一起執行的。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。
  • 為同步時:和串行隊列基本一致。

同步:會先阻塞當前線程,直到任務完成。

異步:會另外開線程處理,不會阻塞當前線程。

串行情況下的 同步和異步

  func testFive() {
        //串行隊列
        let qe =  DispatchQueue.init(label: "concurrentQueue")
       
        print("當前線程",Thread.current)
        //同步
        qe.sync {
            for i in 0..<5 {
                print("?? - \(i)",Thread.current)
            }
        }

        qe.sync {
            for i in 0..<5 {
                print("?? - \(i)",Thread.current)
            }
        }
        
        //異步
        qe.async {
            for i in 0..<5 {
                print("???? - \(i)",Thread.current)
            }
        }
        
        qe.async {
            for i in 0..<5 {
                print("???? - \(i)",Thread.current)
            }
        }
    }

當前線程 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 0 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 1 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 2 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 3 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 4 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 0 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 1 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 2 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 3 <NSThread: 0x60800006d540>{number = 1, name = main}
?? - 4 <NSThread: 0x60800006d540>{number = 1, name = main}
???? - 0 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 1 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 2 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 3 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 4 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 0 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 1 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 2 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 3 <NSThread: 0x600000078f40>{number = 3, name = (null)}
???? - 4 <NSThread: 0x600000078f40>{number = 3, name = (null)}

結論:可以看出,在串行情況下,不管是異步還是同步都是一個個按照順序執行。唯一的區別就是異步單獨開了個線程

并行情況下的 同步和異步

  func testFive() {
        //其實就是初始化函數里,多了個concurrent 的參數
        let qe =  DispatchQueue.init(label: "concurrentQueue", attributes:.concurrent)
       
        print("當前線程",Thread.current)
        
        //同步
        qe.sync {
            for i in 0..<5 {
                print("?? - \(i)",Thread.current)
            }
        }

        qe.sync {
            for i in 0..<5 {
                print("?? - \(i)",Thread.current)
            }
        }
        
        //異步
        qe.async {
            for i in 0..<5 {
                print("???? - \(i)",Thread.current)
            }
        }
        
        qe.async {
            for i in 0..<5 {
                print("???? - \(i)",Thread.current)
            }
        }
    }
    
    當前線程 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 0 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 1 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 2 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 3 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 4 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 0 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 1 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 2 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 3 <NSThread: 0x60000006b380>{number = 1, name = main}
?? - 4 <NSThread: 0x60000006b380>{number = 1, name = main}
???? - 0 <NSThread: 0x608000078640>{number = 3, name = (null)}
???? - 0 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
???? - 1 <NSThread: 0x608000078640>{number = 3, name = (null)}
???? - 1 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
???? - 2 <NSThread: 0x608000078640>{number = 3, name = (null)}
???? - 2 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
???? - 3 <NSThread: 0x608000078640>{number = 3, name = (null)}
???? - 3 <NSThread: 0x6000000767c0>{number = 4, name = (null)}
???? - 4 <NSThread: 0x608000078640>{number = 3, name = (null)}
???? - 4 <NSThread: 0x6000000767c0>{number = 4, name = (null)}

結論:可以看出,在并行情況下,同步時和串行一致。異步則會開多個線程并行執行。

當然,如果你還是有點暈的話可以看下面這個表格,一般情況都可以用下面的表格解釋(例外看下面的注意點!)

同步執行 異步執行
串行隊列 當前線程,一個一個執行 其他線程,一個一個執行
并行隊列 當前線程,一個一個執行 開很多線程,一起執行

注意點:經過測試,得到的經驗:(1)主線程中的其他隊列異步都能分出線程(系統線程資源沒用完前),(2)分線程中的其他隊列異步不一定分出線程;(3)主線程中的主隊列異步不能分出線程。

<a name="線程死鎖解析"></a>線程死鎖解析

一、串行隊列

  • 1、主線程死鎖
    func syncFirst()  {
        print("MainQueue-Task1",Thread.current)
        DispatchQueue.main.sync {
            print("MainQueue-Task2-sync",Thread.current)
        }
        print("MainQueue-Task3",Thread.current)
    }
        
    輸出:1
    MainQueue-Task1 <NSThread: 0x60000006e640>{number = 1, name = main}

解析:

在主隊列存在3個任務(任務1,同步任務2,任務3),按照正常的隊列的順序執行順序應該為
任務1-->同步任務2-->任務3;但是因為同步任務2的存在,導致主線程阻塞,且同步任務2被抽出重新入隊列,于是同步任務2,在主隊列中排在了任務3的后面。那么問題來了,現在有兩條線 1: 任務1-->同步任務2-->任務3 // 2: 任務1-->任務3-->同步任務2 于是主線程就會因為互相等待不知道先執行哪個而完全阻塞。

如下圖所示

gcd-deadlock-1.png
  • 2、其他隊列的阻塞
    func syncSec() {
        let queue = DispatchQueue.init(label: "MyQueue")
        print("MainQueue-Task1",Thread.current) 
        queue.async {
            print("MyQueue-Task2",Thread.current)
            queue.sync(execute: {
                print("MyQueue-Task3",Thread.current)
            })
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    輸出:1 -(2/5)
    MainQueue-Task1 <NSThread: 0x608000071400>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x608000071400>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x60000007d200>{number = 3, name = (null)}

解析:
在主隊列存在3個任務(任務1,異步任務,任務5),那么毫無疑問任務1最先執行,因中間隔著是異步線程,故任務5可以和異步線程同時開始執行;MyQueue串行隊列中存在3個任務(同步任務2,同步任務,同步任務),任務2先執行,然后遇到了同步操作,那么還是和上個問題一樣,同步任務3和任務4之間相互等待,造成 線程<NSThread: 0x60000007d200>的完全阻塞,程序停止;

如下圖所示

線程死鎖2.png
  • 3、錯覺:只要串行隊列中有同步操作,就會立馬死鎖?

看完第一個和第二個的例子是不是有一種錯覺:只要串行隊列中有同步操作,就會立馬死鎖?其實不然,下面的例子就證明了有串行同步操作也是可以的。

    func syncThree()  {
        let qe =  DispatchQueue.init(label: "MyQueue")
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            print("MyQueue-Task3",Thread.current)
        }
        print("MainQueue-Task4",Thread.current)
    }
     輸出:1-2-3 ,且沒有推出程序
     MainQueue-Task1 <NSThread: 0x600000079000>{number = 1, name = main}
     MyQueue-Task2 <NSThread: 0x600000079000>{number = 1, name = main}
     MyQueue-Task3 <NSThread: 0x600000079000>{number = 1, name = main}
     MainQueue-Task4 <NSThread: 0x600000079000>{number = 1, name = main}

解析:
主隊列中3個任務(任務1,同步任務,任務4),MyQueue隊列中兩個任務(任務2,任務3)。主線程首選執行主隊列中的任務1,然后遇到了同步任務,關鍵來了!因同步任務是MyQueue的全部任務集合,故不會和主隊列進行沖突,而是按照官方描述的,遇到同步任務要執行完任務中的事情,于是任務2和任務3相繼被執行,之后主隊列的任務4出隊列被執行! 順序為:1->2->3->4。

如下圖所示

線程死鎖3.png
  • 4、串行隊列下到底怎樣的情況下才會造成死鎖?
    func syncFour()  {
        let qe =  DispatchQueue.init(label: "MyQueue")//, attributes:.concurrent)
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            qe.sync {
                print("MyQueue-Task3",Thread.current)
            }
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    輸出 1-2
    MainQueue-Task1 <NSThread: 0x60800006f240>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x60800006f240>{number = 1, name = main}
    

解析:
主隊列中3個任務(任務1,同步任務,任務5),MyQueue隊列中三個任務(任務2,同步任務3,任務4)。
主線程首先執行任務1,之后遇到MyQueue的同步任務,跳到MyQueue中執行任務2,又遇到了MyQueue的同步任務,那么同步任務中的任務3被推入隊列中,導致任務3跑到任務4的后面,導致了任務3和任務4的相互等待,造成死鎖!
我們回到第一個例子的代碼中再思考一下,和這段代碼有沒有共同點??其實我們可以理解為下面這段偽代碼

    //偽代碼,為了說明
    MainQueue.sync{
        viewDidload{
                ....
                syncFirst();
                ....
        }
    }
    
    func syncFirst()  {
        print("MainQueue-Task1",Thread.current)
        DispatchQueue.main.sync {
            print("MainQueue-Task2-sync",Thread.current)
        }
        print("MainQueue-Task3",Thread.current)
    }

是不是明白了什么?整個主線程已經是在串行同步的條件下了,
所以我么可以總結一下串行隊列的死鎖情況:串行隊列中有屬于自身隊列的同步操作,就會立馬死鎖!或者說 任何一個串行隊列,不能添加兩個本隊列的同步操作!

如下圖所示

線程死鎖4.png

二、并行隊列

  • 1、并行隊列下的同步
    func syncFive()  {
        let qe =  DispatchQueue.init(label: "MyQueue", attributes:.concurrent)
        print("MainQueue-Task1",Thread.current)
        qe.sync {
            print("MyQueue-Task2",Thread.current)
            qe.sync {
                print("MyQueue-Task3",Thread.current)
            }
            print("MyQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
    }
    輸出:1-2-3-4-5
    MainQueue-Task1 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task2 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task3 <NSThread: 0x608000076480>{number = 1, name = main}
    MyQueue-Task4 <NSThread: 0x608000076480>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x608000076480>{number = 1, name = main}

解析
主隊列中3個任務(任務1,同步任務,任務5),MyQueue隊列中三個任務(任務2,同步任務3,任務4)。
主線程首先執行任務1,遇到MyQueue的同步任務跳到MyQueue隊列中,執行任務2,此時遇到了MyQueue自己隊列的同步任務3,因是并行情況所有所以直接執行(ps:至于為什么直接過去了,我還沒找到原理般的解釋,先相信知乎上這篇文章上第一個回答所說的,如果大家知道原理,可以告訴我。)
執行完成后再回到主隊列,執行任務5。

如下圖所示

線程死鎖5.png
  • 2、 并行下也是有可能出現死鎖的,別大意
    func testThree()  {
        // 全局線程
        let queueGlobal = DispatchQueue.global()
        // 主線程
        let queueMain = DispatchQueue.main
        print("MainQueue-Task1",Thread.current)
        //全局異步
        queueGlobal.async {
            print("GlobalQueue-Task2 ",Thread.current)
            //主線程同步,因主線程阻塞,block內容和while循環相互等待
            queueMain.sync(execute: {
                print("GlobalQueue-Task3 ",Thread.current)
            })
            print("GlobalQueue-Task4",Thread.current)
        }
        print("MainQueue-Task5",Thread.current)
        sleep(3);
        print("MainQueue-Task6")
    }
    輸出 1-(5/2)-6-3-4
    MainQueue-Task1 <NSThread: 0x60000007d4c0>{number = 1, name = main}
    MainQueue-Task5 <NSThread: 0x60000007d4c0>{number = 1, name = main}
    GlobalQueue-Task2  <NSThread: 0x608000269680>{number = 3, name = (null)}
    MainQueue-Task6
    GlobalQueue-Task3  <NSThread: 0x60000007d4c0>{number = 1, name = main}
    GlobalQueue-Task4 <NSThread: 0x608000269680>{number = 3, name = (null)}


解析
看圖就知道這個比較麻煩!主隊列中6個任務(任務1,異步任務,任務5,休眠任務,任務6,和后面入對的任務3),GlobleQueue隊列中三個任務(任務2,同步任務,任務4)。主線程執行任務1,遇到異步則分出線程執行任務2,同時主線程繼續執行任務5,所以任務2和5位置不一定。現在有兩條線程,主線程上進入了3秒休眠。分線程上因遇到主線程的同步任務,主線程為串行同步隊列,故需要把任務3加入到主線程的隊尾,在任務6之后。休眠結束后,主線程繼續任務6和任務3,任務3執行完之后,同步任務才得以完成,所以任務4才能執行。有點繞,要消化一下!

如下圖所示

線程死鎖6.png

<a name="DispatchQueue的使用"></a>DispatchQueue的使用

初始化

 let queue = DispatchQueue.init(label:String,qos:DispatchQoS,attributes:DispatchQueue.Attributes,autoreleaseFrequency:DispatchQueue.AutoreleaseFrequency, target: DispatchQueue?)

屬性

  • label 隊列的標識 (required字段,下面都是option字段)
  • qos (quality of server) 服務質量,也可以說是優先級,優先級高的得到的資源和權限就越高!下面為從高到低的優先級
    1. userInteractive
    2. userInitiated
    3. default
    4. utility
    5. background
    6. unspecified
  • attributes
    1. concurrent: 并行自動模式
    2. initiallyInactive: 還是串行,但需要手動啟動 (如queue.activate())
  • autoreleaseFrequency
    1. inherit 繼承
    2. workItem 工作項目
    3. never
  • target 新的一個queue。注意:如果傳這個參數的話,前面的配置無效,全部按照新傳入的queue本身的配置。(親測)

<a name="Dispatchgroup的使用"></a>Dispatchgroup的使用

初始化

 let group = DispatchGroup.init()

實戰

下面的一段代碼很好的解決了多個網絡請求并行的問題。

    func testGroup() {
        
        let group = DispatchGroup.init()
        let queueOne = DispatchQueue.init(label: "queueOne",attributes:.concurrent)
        
        let firstTime = NSDate.init().timeIntervalSince1970
        print("主線程",Thread.current)
        print("開始時間",firstTime)
          //這里和下面的三段模擬日常的網絡請求
        group.enter()
        queueOne.async(group: group) {
            sleep(1) //模擬請求延遲
            print("1結束")
            group.leave()
        }
        
        group.enter()
        queueOne.async(group: group) {
            sleep(2)
            print("2結束")
            group.leave()
        }
        
        group.enter()
        queueOne.async(group: group) {
            sleep(3)
            print("3結束")
            group.leave()
        }
        
        group.notify(queue: DispatchQueue.main) {
            let secTime = NSDate.init().timeIntervalSince1970
            print("所有異步執行結束時間",secTime)
            print("主線程",Thread.current)
        }
    }
    輸出:
    主線程 <NSThread: 0x60000006e800>{number = 1, name = main}
    開始時間 1487831647.5901
    1結束
    2結束
    3結束
    所有異步執行結束時間 1487831650.66429
    主線程 <NSThread: 0x60000006e800>{number = 1, name = main}

解析
首先先創建一個group和一個并行的queue;然后往group里添加三段 并行queue異步的模擬網絡請求,分別延遲1s,2s,3s。看輸出你就可以看出,整個三段請求的時間是按照 請求時間最大的一段來決定的,所以是3s的時間。等所有的請求都完成之后,就會執行group的notify回調,傳人主線程,就可以刷新UI拉。

注意點
enter 和 leave實際執行的次數得是1:1的,不然就會crash。所以我們平時就可以在網絡請求的成功和失敗block各放一個leave。因其要么失敗要么成功,比例還是1:1。

<a name="DispatchSourceTiemr的使用"></a>DispatchSourceTiemr的使用

初始化

(評論中有朋友遇到過 定時器不執行的問題,是因為 沒把 定時器全局化 。如:)
 var timer:DispatchSourceTimer? 感謝XIAODAO同學 的建議,哈哈)
 let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags.strict, queue: DispatchQueue.main)
 當然也可以簡單的初始化:
 let timer = DispatchSource.makeTimerSource()//默認了主線程

注意點

timer要全局定義,上面那樣局部定義初始化是不會執行的

屬性

  • flags 一般不用傳
  • queue 定時器運行的隊列,傳入主隊列 在主線程執行;不傳則默認在分線程

主要方法

  • scheduleOneshot 循環單次

  • scheduleRepeating 重復循環

    兩個方法的屬性

    • wallDeadline 延遲執行
    • leeway 允許的誤差
    • deadline 執行的時間
    • interval 循環模式時的間隔時間

來看幾段代碼

單次執行

    func runOnce()  {
        print("主線程",Thread.current)
        //創建主線程下的定時器
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
        
        //單次執行 
        timer?.scheduleOneshot(deadline: DispatchTime.now(), leeway: .milliseconds(10))
        
        //執行的代碼塊
        timer?.setEventHandler {
            print("單次執行",Thread.current)
        }
        //繼續,用activate()也行
        timer?.resume()
    } 
    輸出:
    主線程 <NSThread: 0x6080000740c0>{number = 1, name = main}
    單次執行 <NSThread: 0x6080000740c0>{number = 1, name = main}

多次執行

    func runMul()  {
        print("主線程",Thread.current)
        創建分線程下的定時器
        timer = DispatchSource.makeTimerSource()
        
        //循環執行,馬上開始,間隔為1s,誤差允許10微秒
        timer?.scheduleRepeating(deadline: DispatchTime.now(), interval: .seconds(1), leeway: .milliseconds(10))
        
        //執行內容
        timer?.setEventHandler {
            print("1 second interval",Thread.current)
        }
        
        //激活
        timer?.activate()
    }
    輸出:
    主線程 <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f0c0>{number = 1, name = main}
    ...
    ...

取消執行

    func runCancel()  {
        print("主線程",Thread.current)
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
        
        timer?.scheduleRepeating(deadline: DispatchTime.now(), interval: .seconds(1), leeway: .milliseconds(10))
        
        timer?.setEventHandler {
            print("1 second interval",Thread.current)
            sleep(2)
            print("after sleep")
        }
        
        timer?.activate()
        
        //主隊列創建的情況下 如果在這里直接cancel(),handler里面的內容是不能執行的。如果是默認的分線程,則是可以的,至于為什么,我前面多線程的時候解釋過了哈。
         //timer?.cancel()
         
         
        
        //最好都這樣取消
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: {
            self.timer?.cancel()
        })
    }
    輸出:
    主線程 <NSThread: 0x6080000644c0>{number = 1, name = main}
    1 second interval <NSThread: 0x60000006f000>{number = 3, name = (null)}
    after sleep

<a name="延遲執行"></a>延遲執行

GCD的延遲執行一般來說可以分為兩大類

  • 1、 隊列直接asyncAfter
  • 2、 定時器

如下代碼所示

    func testDelay()  {
    
            let queue = DispatchQueue.init(label: "myqueue")
            let delayTime = DispatchTime.now() + 2.0
            let delayTimeTimer =  DispatchWallTime.now() + 2.0
            print("before delay")
            
            //第一種
            queue.asyncAfter(deadline: delayTime) {
                print("這是 asyncAfter delay")
            }
            
            //第二種
            let workItem = DispatchWorkItem.init {
                print("這是 asyncAfter workItem delay")
            }
            queue.asyncAfter(deadline: delayTime, execute: workItem)
        
            //第三種
            timer = DispatchSource.makeTimerSource()
            timer?.scheduleOneshot(wallDeadline:delayTimeTimer)
            timer?.setEventHandler(handler: {
                print("這是 timer delay")
            })
            timer?.activate()
    }
    輸出:
    before delay
    這是 timer delay
    這是 asyncAfter delay
    這是 asyncAfter workItem delay

總結

針對本文的觀點,如有錯誤點,煩請指出!

本文引用以下文章的部分觀點:五個案例讓你明白GCD死鎖關于iOS多線程,你看我就夠了

作者:燈泡蟲

郵箱:developer_yh@163.com

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

推薦閱讀更多精彩內容