GCD简介

GCD简介

什么是GCD

  • 全称是Grand Central Dispatch
  • 纯C语言的,提供了非常多强大的函数.

GCD的优势

  • GCD是苹果公司为多核的并行运算提出的解决方案.
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程).
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码.

GCD的核心

  • 将任务添加到队列
  • 任务 : 执行什么操作
  • 队列 : 用来存放任务

GCD使用的两个步骤

  • 创建任务 : 确定要做的事情
    • GCD中的任务是使用BLOCK封装的.
  • 将任务添加到队列中
    • GCD会自动将队列中的任务取出,放到对应的线程中执行.
    • 任务的取出遵循队列的FIFO原则 : 先进先出,后进后出.
队列.png

GCD的常用代码

GCD的核心

  • 将任务添加到队列
  • 任务 : 执行什么操作
  • 队列 : 用来存放任务

队列和任务简介

队列

  • GCD的队列可以分为2大类型 :

  • 串行队列(Serial Dispatch Queue)

    • 让任务一个接着一个有序的执行:不管队列里面放的是什么任务.一个任务执行完毕后,再执行下一个任务.
    • 同时只能调度一个任务执行.
  • 并发队列(Concurrent Dispatch Queue)

    • 可以让多个任务并发/同时执行.自动开启多个线程同时执行多个任务.
    • 同时可以调度多个任务执行
    • 并发队列的并发功能只有内部的任务是异步任务时,才有效.

任务

  • GCD中有2个用来执行任务的函数 :

  • 同步的方式执行任务 : 在当前线程依次执行任务

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
* queue:队列
* block:任务
  • 异步的方式执行任务 : 新开线程,在新线程中执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
* queue:队列
* block:任务

串行队列和并发队列图解

串行队列

串行队列01.png
串行队列02.png

并发队列

并发队列01.png
并发队列02.png

队列和任务组合的总结

  • 同步和异步决定了要不要开启新的线程 (同步不开,异步开)

    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 串行和并发决定了任务的执行方式

    • 串行:一个任务执行完毕后,再执行下一个任务
    • 并发:多个任务并发(同时)执行
  • 当任务是异步的时候,队列决定了开启多少条线程

    • 串行队列 : 只开一条
    • 并发队列 : 可以开启多条
队列和任务组合的总结.png

代码演练

任务添加到队列分开写

/// 队列+任务
- (void)gcdDemo1
{
    // 全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 任务
    void (^task)() = ^ {
        NSLog(@"%@",[NSThread currentThread]);
    };

    // 同步任务
//    dispatch_sync(queue, task);

    // 异步任务 : 每次执行任务的线程不一定是一样的
    dispatch_async(queue, task);

    NSLog(@"end");
}

简写

- (void)gcdDemo2
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

线程间通信

/// 线程间通信
- (void)gcdDemo3
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"下载中... %@",[NSThread currentThread]);

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"下载完成 %@",[NSThread currentThread]);
        });
    });
}

使用GCD修改异步下载网络图片的代码

- (void)downloadImage
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 获取网络图片地址
        NSURL *url = [NSURL URLWithString:@"http://photocdn.sohu.com/20151209/mp47379110_1449633737507_2_th.png"];
        // 获取网络图片二进制数据
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 获取图片对象
        UIImage *image = [UIImage imageWithData:data];

        // 图片下载完成之后,回到主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            // 设置图片视图
            self.imageView.image = image;
            // 图片视图自适应图片的大小
            [self.imageView sizeToFit];
            // 设置滚动视图
            [self.scrollView setContentSize:image.size];
        });
    });
}

总结 : 与 NSThread 的对比

  • 所有的代码写在一起的,让代码更加简单,易于阅读和维护
  • NSThread 通过 @selector 指定要执行的方法,代码分散
  • GCD 通过 block 指定要执行的代码,代码集中
  • 使用 GCD 不需要管理线程的创建/销毁/复用的过程.程序员不用关心线程的生命周期
  • 如果要开多个线程 NSThread 必须实例化多个线程对象
  • NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block

串行队列

特点

  • 以先进先出的方式,顺序调度队列中的任务执行
  • 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

代码演练

队列创建

// 参数1 : 队列的标示符
// 参数2 : 队列的属性.决定了队列是串行的还是并行的.
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);

串行队列+同步任务

#pragma mark - 串行队列+同步任务
/*
    1. 没有开新线程
    2. 循环是顺序打印
    3. @"end"最后执行
 */
- (void)gcdDemo1
{
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);

    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"end");
}
num04.png

串行队列+异步任务

#pragma mark - 串行队列+异步任务
/*
    1. 开一条新线程 : 因为队列是顺序调度任务,前一个任务执行完成以后才能调度后面的任务,开一条新线程就够了
    2. 循环是顺序打印
    3. @"end"不是在最后执行
 */
- (void)gcdDemo2
{
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);

    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"end");
}
num05.png

并发队列

特点

  • 以先进先出的方式,并发调度队列中的任务执行
  • 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
  • 如果当前调度的任务是异步执行的,会调度多个线程同时执行多个任务.

代码演练

队列创建

// 参数1 : 队列的标示符
// 参数2 : 队列的属性.决定了队列是串行的还是并行的.
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

并发队列+同步任务

#pragma mark - 并发队列+同步任务
/*
    1. 没有开新线程
    2. 循环顺序打印
    3. @"end"最后执行
 */
- (void)gcdDemo1
{
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"end");
}
num06.png

并发队列+异步任务

#pragma mark - 并发队列+同步任务
/*
 1. 开启多条新线程
 2. 循环无序打印
 3. @"end"不是最后执行
 */
- (void)gcdDemo2
{
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });
    }

    NSLog(@"end");
}
num07.png

主队列

特点

  • 专门用来在主线程上调度任务的队列.
  • 不会开启新线程.
  • 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行.
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度.

主队列创建

  • 主队列是负责在主线程调度任务的,会随着程序启动一起创建,只需要获取不用创建.
dispatch_queue_t queue = dispatch_get_main_queue();

代码演练

主队列+异步任务

#pragma mark - 主队列+异步任务
/*
    1. 执行顺序 : @"start"->@"end"->执行中...
    2. 没有开启新线程
 */
- (void)gcdDemo1
{
    // 主队列 : 程序一启动就创建好的,不需要创建
    dispatch_queue_t queue = dispatch_get_main_queue();

    NSLog(@"start");

    dispatch_async(queue, ^{
        NSLog(@"执行中...%@",[NSThread currentThread]);
    });

    NSLog(@"end");
}

主队列+同步任务=死锁

#pragma mark - 主队列+同步任务=死锁
/*
    1. 执行顺序 : @"start"   后面的任务被阻塞了
    2. 同步任务和主线程相互等待,造成线程死锁
 */
- (void)gcdDemo2
{
    dispatch_queue_t queue = dispatch_get_main_queue();

    NSLog(@"start");

    dispatch_sync(queue, ^{
        NSLog(@"执行中...%@",[NSThread currentThread]);
    });

    NSLog(@"end");
}

死锁解决办法

#pragma mark - 死锁解决办法
// 主队列中的同步任务放进子线程中,不使其阻塞主线程
- (void)gcdDemo3
{
    dispatch_async(dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT), ^{

        NSLog(@"start");

        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"执行中...%@",[NSThread currentThread]);
        });

        NSLog(@"end");
    });
}

全局队列

全局队列 & 并发队列的区别

  • 全局队列又叫全局并发队列.
  • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致
  • 全局队列
    • 没有名称
    • 无论 MRC & ARC 都不需要考虑释放
    • 日常开发中,建议使用"全局队列"
  • 并发队列
    • 有名字,和 NSThreadname 属性作用类似
    • 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象
    • 开发第三方框架时,建议使用并发队列

代码演练

全局队列+异步任务

  • 运行效果和并发队列一样
#pragma mark - 全局队列
// 执行效果跟并发队列一样的
- (void)gcdDemo
{
    // 全局队列,跟主队列一样不需要创建
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });

        /*
        dispatch_sync(queue, ^{
            NSLog(@"%d %@",i,[NSThread currentThread]);
        });
        */
    }

    NSLog(@"end");
}

参数

  1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

    • iOS 8.0(新增,暂时不能用)
      • QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
      • QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
      • QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
      • QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
      • QOS_CLASS_BACKGROUND 0x09, 后台
      • QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
    • iOS 7.0
      • DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
      • DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
      • DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
      • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
  2. 为未来保留使用的,应该永远传入0

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码:
dispatch_get_global_queue(0, 0);

同步任务的作用

使用场景

  • 当多个任务之间有依赖关系时,可以将任务定义成同步的.
  • 需求 : 登陆->付费->下载->通知用户

代码演练

建立依赖关系

  • 使用同步任务来建立任务之间的依赖关系
- (void)GCDDemo1
{
    // 创建全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_sync(queue, ^{
        // 查看当前线程
        NSLog(@"登陆 %@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        // 查看当前线程
        NSLog(@"付费 %@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        // 查看当前线程
        NSLog(@"下载 %@",[NSThread currentThread]);
    });

    dispatch_async(dispatch_get_main_queue(), ^{
        // 查看当前线程
        NSLog(@"通知用户 %@",[NSThread currentThread]);
    });
}
  • 问题 : 依赖关系虽然有了,但是耗时的网络操作都在主线程中执行的.需要优化.

依赖关系的优化

  • 使同步任务在子线程中建立依赖关系
- (void)GCDDemo2
{
    // 创建全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{

        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"登陆 %@",[NSThread currentThread]);
        });

        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"付费 %@",[NSThread currentThread]);
        });

        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"下载 %@",[NSThread currentThread]);
        });

        dispatch_async(dispatch_get_main_queue(), ^{
            // 查看当前线程
            NSLog(@"通知用户 %@",[NSThread currentThread]);
        });
    });
}

异步Barrier

使用场景

  • 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行操作
  • 适合于大规模数据的 I/O (读写) 操作

代码演练

  • 定义图片容器
/// 图片容器
@property (nonatomic,strong) NSMutableArray *imagesM;
  • 懒加载(NSMutableArray是线程不安全的)
- (NSMutableArray *)imagesM
{
    if (_imagesM==nil) {
        _imagesM = [[NSMutableArray alloc] init];
    }
    return _imagesM;
}
  • 循环下载
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    for (int i = 0; i < 10; i++) {
        [self downloadImages:i];
    }
}
/// 下载图片
- (void)downloadImages:(int)index
{
    dispatch_async(dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT), ^{
        // 获取文件
        NSString *imageName = [NSString stringWithFormat:@"%02d.jpg",index+1];
        NSString *fileName = [[NSBundle mainBundle] pathForResource:imageName ofType:nil];

        // 获取图片
        UIImage *image = [UIImage imageWithContentsOfFile:fileName];
        // 模拟下载图片的延时
        [NSThread sleepForTimeInterval:1.0];

        // 添加图片到容器:可变数组是线程不安全的类,多个线程同时操作数组的读写,会出错
        [self.imagesM addObject:image];

        NSLog(@"图片下载完成 %zd",self.imagesM.count);
    });
}
  • 由于NSMutableArray是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况
  • 解决办法:添加Barrier
dispatch_barrier_async(_queue, ^{

    // 在同一个线程中,循环有序的添加image
    [self.imagesM addObject:image];

    NSLog(@"图片下载完成 %zd %@",self.imagesM.count,[NSThread currentThread]);
});
  • 使用 (dispatch_barrier_async)添加的block会在之前添加的block全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作
  • 注意:(dispatch_barrier_async)必须使用自定义队列,否则执行效果和全局队列一致

一次性执行(once)

  • dispatch_once_t内部有一把锁,是能够保证线程安全.而且是苹果公司推荐使用的.
  • 在开发中,有些代码只想就只执行一次.典型的应用场景就是设计单例模式.

验证一次性执行的可靠性

- (void)onceDemo1
{
    NSLog(@"mark");

    static dispatch_once_t onceToken;

    NSLog(@"返回值 %ld",onceToken);

    dispatch_once(&onceToken, ^{
        NSLog(@"hello");
    });
}
  • 原理 : onceToken有个初始值,当第一次执行时,判断是否是初始值,如果是初始值就执行函数内部的代码,执行结束之前会修改onceToken初始值.反之,就不执行.

异步并发任务中验证执行一次代码的安全性

- (void)onceDemo2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 多线程环境下测试安全性
    for (int i = 0; i < 1000; i++) {

        NSLog(@"%d",i);

        dispatch_async(queue, ^{
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                NSLog(@"hello");
            });
        });
    }
}

设计单例模式

单例模式的特点

  1. 有一个能够方便外界调用的类方法,用来实例化单例对象
  2. 保证这个类在程序运行的过程当中,在内存中有且只有一个实例化的对象
  3. 保存在内存中的静态区 (保证了单例的生命周期和程序的一样长)

单例实现

定义外界调用实例化单例对象的类方法

@interface AccountManager : NSObject

/// 懒加载的方式,实例化单例对象
+ (instancetype)lazyAccountManager;

/// once的方式,实例化单例对象
+ (instancetype)sharedAccountManager;

@end

懒加载和互斥锁设计单例模式

+ (instancetype)lazyAccountManager
{
    // 定义一个对象
    // static : 保证某个类,实例化的对象,在内存中只保存一份
    static AccountManager *manager;

    // 为保证在多线程的环境下,线程的安全,所以添加了一把互斥锁
    // 但是互斥锁的性能有点儿差
    @synchronized(self) {
        // 判断内存中有没有这个对象,如果没有,就创建
        if (manager == nil) {
            manager = [[AccountManager alloc] init];
        }
    }

    return manager;
}

once设计单例模式

+ (instancetype)sharedAccountManager
{
    // 存放在内存中的静态区
    static AccountManager *manager;

    // 保证只实例化了一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[AccountManager alloc] init];
    });
    return manager;
}
  • 面试的时候会要求手写单例设计模式 (dispatch_once_t)

性能测试

- (void)onceDemo
{
    double start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 1000; i++) {
        AccountManager *manager1 = [AccountManager lazyAccountManager];
    }

    // 0.000254
    NSLog(@"lazy %f",CFAbsoluteTimeGetCurrent() - start);


    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 1000; i++) {
        AccountManager *manager3 = [AccountManager sharedAccountManager];
    }

    // 0.000159
    NSLog(@"shared %f",CFAbsoluteTimeGetCurrent() - start);
}

结论

  • once和互斥锁都能够保证线程安全.
  • once的性能比互斥锁高.
  • once设计单例更加简便.

调度组(group)

调度组原理.png

特点

  • 调度组中的所有异步任务执行结束之后,会得到统一的通知.

使用场景

  • 监听一组异步任务是否执行结束,如果执行结束就能够得到统一的通知.

常规用法

- (void)gcdDemo1
{
    // 调度组
    dispatch_group_t group = dispatch_group_create();

    // 队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

    // 将任务添加到队列和调度组
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载 A %@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queue, ^{
        NSLog(@"下载 B %@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queue, ^{
        NSLog(@"下载 C %@",[NSThread currentThread]);
    });

    // 异步 : 调度组中的所有异步任务执行结束之后,在这里得到统一的通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"下载完成 %@",[NSThread currentThread]);
    });

    // 同步 : 一直等到调度组中所有的任务都执行结束以后才执行后面的代码
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    // 验证调度组是否是异步
    NSLog(@"end");
}

调度组原理

The dispatch_group_async() convenience function behaves like so:

void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispat
                     ch_block_t block)
{
    dispatch_retain(group);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        block();
        dispatch_group_leave(group);
        dispatch_release(group);
    });
}
  • 调度组原理实现监听一组异步任务是否执行结束(任务是网络回调的时候用以下方法,应为走完代码块group就会认为任务执行完成,如果不用enter和leave的话)
- (void)gcdDemo2
{
    // 调度组
    dispatch_group_t group = dispatch_group_create();

    // 队列
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

    // dispatch_group_enter 和 dispatch_group_leave 必须成对出现
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{

        NSLog(@"下载 A %@",[NSThread currentThread]);

        // dispatch_group_leave 必须是 block 的最后一句
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载 B %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载 C %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    // 异步 : 调度组中的所有异步任务执行结束之后,在这里得到统一的通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"下载完成 %@",[NSThread currentThread]);
    });

    // 同步 : 一直等到调度组中所有的任务都执行结束以后才执行后面的代码
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    // 验证调度组是否是异步
    NSLog(@"end");
}

GCD延时操作

代码演练

- (void)after
{
    NSLog(@"start");

    /*
     参数1 : dispatch_time_t when,表示延迟的时间
     参数2 : dispatch_queue_t queue,表示任务执行的队列
     参数3 : dispatch_block_t block,表示线程要执行的任务
     */

    // 参数1 : 从现在开始,延迟多长时间,精确到纳秒
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));

    // 参数2 : 队列是可以变化的
//    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 参数3
    void (^task)() = ^{
        // 查看当前线程
        NSLog(@"%@",[NSThread currentThread]);
    };

    // 延迟多少纳秒,在哪个队列中调度执行哪个任务
    dispatch_after(when, queue, task);
}

结论

  • 延迟多少纳秒,在哪个队列中调度执行哪个任务
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容