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阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容