iOS 多线程系列 -- GCD全解二(常用方法)

iOS 多线程系列 -- 基础概述
iOS 多线程系列 -- pthread
iOS 多线程系列 -- NSThread
iOS 多线程系列 -- GCD全解一(基础)
iOS 多线程系列 -- GCD全解二(常用方法)
iOS 多线程系列 -- GCD全解三(进阶)
iOS 多线程系列 -- NSOperation
测试Demo的GitHub地址

3. GCD常用方法

3.1 Dispatch Group

Dispatch Group 可以让我们很方便的控制多线程中任务执行顺序。假设这样一种需求,有三个任务OA/OB/OC,我们想让OC在OAOB执行完毕在执行,有几种实现方式?方式有很多,详细看下面总结,其中一种方式我们就可以用Dispatch Group实现。

  • 数据类型:
    • dispatch_group_t : 组对象,调用dispatch_group_create()创建
常用方法:
  • dispatch_group_async/dispatch_group_async_f ,异步方式将代码块block放入队列queue中执行,并将队列关联到调度组group。dispatch_group会等和它关联的所有的dispatch_queue_t上的任务都执行完毕才会发出同步信号,执行dispathc_group_notify的代码块block,同时dispatch_group_wati会结束等待。一个group可以关联多个任务队列,下面示例代码中可以查看。
    dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i<10; i++) {
            NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
        }
    });
  • dispatch_group_notify 调度组Dispatch Group中任务执行完毕后,会发出同步信号,执行dispatch_group_notify中的block,以此来实现线程同步,实现多线程任务顺序控制。所以上面的OA/OB/OC问题可以这样:
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    //任务OA
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    //任务OB
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //任务OC
});
  • dispatch_group_wait 同步函数,会阻塞当前线程直到超时或者group任务完成。返回0表示任务完全执行完毕,非0表示超时。如果想要一直等待直到任务完成,可以把第二个时间参数设置为DISPATCH_TIME_FOREVER.因为同步,所以小心死锁,尽量不要放在主线程调用此方法
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); 
  • dispatch_group_enter 手动指示有一个block进入group.调用此函数表示另一个块通过加入组除dispatch_group_async()之外的一种手段。调用此函数必须是与dispatch_group_leave()平衡,否则调度组会显示一个有任务,不会调用dispatch_group_notify.
  • dispatch_group_leave 手动指示group中一个block完成.调用此函数表示块已完成,并通过除dispatch_group_async()之外的方法离开调度组.
  • dispatch_group_enter和dispatch_group_leave的简单理解
  • 一种不同于dispatch_group_async的操作调度组的方法
  • 当我们调用n次dispatch_group_enter后再调用n次dispatch_group_level时,dispatch_group_notify和dispatch_group_wait会收到同步信号
  • 可以简单的看做group中有一个表示任务数的属性operationCount, dispatch_group_enter会让operationCount加1, dispatch_group_level会让operationCount减1.当operationCount为0的时候,表示group总任务执行完毕,会同步通知dispatch_group_notify和dispatch_group_wait
  • 测试代码
- (void)group
{
    NSLog(@"---group-begin---%@",[NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{// 异步添加任务到全局并发队列,并关联到调度组
        for (int i = 0; i<2; i++) {
            NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, dispatch_get_main_queue(), ^{// 异步添加任务到主队列,并关联到调度组
        for (int i = 0; i<2; i++) {
            NSLog(@"---group2--%d---%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"---group middle %@",[NSThread currentThread]);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // group中的任务都执行完毕后,才回执行这个block
        NSLog(@"---dispatch_group_notify---%@",[NSThread currentThread]);
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"---group3-----%@",[NSThread currentThread]);
        });
        NSLog(@"---dispatch_group_notify middle %@",[NSThread currentThread]);
        dispatch_group_async(group, dispatch_get_main_queue(), ^{
            NSLog(@"---group4----%@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"---second dispatch_group_notify---%@",[NSThread currentThread]);
        });
    });
    NSLog(@"---before dispatch_group_wait %@",[NSThread currentThread]);
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); // 同步函数,会阻塞当前线程,只要超时或者group任务完成,返回0表示任务完全执行完毕,非0表示超时
    NSLog(@"---group-end---%@",[NSThread currentThread]);
}
  • 打印结果:
2017-06-29 10:38:15.572 Test - 多线程[21587:754475] ---group-begin---<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:15.573 Test - 多线程[21587:754568] ---group1--0---<NSThread: 0x6000002742c0>{number = 3, name = (null)}
 2017-06-29 10:38:15.573 Test - 多线程[21587:754475] ---group middle <NSThread: 0x60000007d200>{number = 1, name = main}  //①
 2017-06-29 10:38:15.573 Test - 多线程[21587:754568] ---group1--1---<NSThread: 0x6000002742c0>{number = 3, name = (null)}  //②
 2017-06-29 10:38:15.573 Test - 多线程[21587:754475] ---before dispatch_group_wait <NSThread: 0x60000007d200>{number = 1, name = main} //③
 2017-06-29 10:38:20.575 Test - 多线程[21587:754475] ---group-end---<NSThread: 0x60000007d200>{number = 1, name = main} //④
 2017-06-29 10:38:20.576 Test - 多线程[21587:754475] ---group2--0---<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.576 Test - 多线程[21587:754475] ---group2--1---<NSThread: 0x60000007d200>{number = 1, name = main} //⑤
 2017-06-29 10:38:20.577 Test - 多线程[21587:754475] ---dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑥
 2017-06-29 10:38:20.577 Test - 多线程[21587:754475] ---dispatch_group_notify middle <NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.577 Test - 多线程[21587:754568] ---group3-----<NSThread: 0x6000002742c0>{number = 3, name = (null)}
 2017-06-29 10:38:20.578 Test - 多线程[21587:754475] ---group4----<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.578 Test - 多线程[21587:754475] ---second dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑦

  • 打印结果分析:
    • 从①和②打印结果看, dispatch_group_async添加任务是异步的,不会阻塞当前线程
    • 从③和④的打印时间差,可以看出当前线程被阻塞了5s,说明dispatch_group_wait是同步函数,会阻塞线程. 为什么阻塞当前线程的时间内没有group2输出,查看添加group2输出的代码发现,这个打印任务是异步添加到主线程的,主线程阻塞完毕以后才会继续打印group2
    • ③的打印顺序还可以说明dispatch_group_notify也是异步添加任务,不会阻塞当前线程
    • ⑥的打印是在group1和group2之后得出,notify中的block任务确实是在group关联的任务执行完毕后才执行
    • group3/group4的打印,以及④的输出可以看出nofity可以多层嵌套
    • 所有调用dispatch_group_async的地方查看一下代码会发现,调度组group可以关联到不同的队列

3.2 栅栏

dispatch_barrier_sync
  • 在一个dispatch queue分派队列上提交用于同步执行的block代码块,类似dispatch_sync()但是会标记为障碍,可以控制多线程中任务执行顺序.执行顺序为:在其之前提交的任务先执行 -> dispatch_barrier_sync提交的任务 -> dispatch_barrier_sync之后提交的任务
  • dispatch_barrier_sync是和并发队列相关联的,因为串行队列中的任务本身就是顺序执行的,不需要barrier技术
  • 同步提交block到指定queue,会阻塞当前线程直到在他前面提交到此queue的任务执行完毕,然后执行barrier block,然后当前线程才能继续往下执行任务 , 从下面测试打印结果的⑤⑥⑦,打印barrier之后才会打印after dispatch_barrier_sync可以验证这一点
  • 函数声明如下:queue是指定的并发队列,block是提交的代码块
void dispatch_barrier_sync(dispatch_queue_t queue,
        DISPATCH_NOESCAPE dispatch_block_t block);
  • 测试代码如下:
 NSLog(@"barrierSync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_sync");
dispatch_barrier_sync(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_sync");

dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierSync end");
  • 测试代码打印如下:
2017-06-29 12:28:15.355 Test - 多线程[22813:835755] barrierSync begin //①
 2017-06-29 12:28:15.355 Test - 多线程[22813:835755] before dispatch_barrier_sync //②
 2017-06-29 12:28:15.355 Test - 多线程[22813:835803] ----2-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //③
 2017-06-29 12:28:15.355 Test - 多线程[22813:835805] ----1-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //④
 2017-06-29 12:28:15.356 Test - 多线程[22813:835755] ----barrier-----<NSThread: 0x600000071880>{number = 1, name = main} //⑤
 2017-06-29 12:28:15.356 Test - 多线程[22813:835755] after dispatch_barrier_sync //⑥
 2017-06-29 12:28:15.356 Test - 多线程[22813:835755] barrierSync end //⑦
 2017-06-29 12:28:15.356 Test - 多线程[22813:835805] ----3-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //⑧
 2017-06-29 12:28:15.356 Test - 多线程[22813:835803] ----4-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //⑨
dispatch_barrier_async,基本和dispatch_barrier_sync类似的功能,区别在于:
  • dispatch_barrier_async是提交异步执行的block代码块,不会阻塞当前线程,从下面测试打印结果的⑤⑥⑦after dispatch_barrier_sync在barrier之前打印可以验证这一点
  • 测试代码:
NSLog(@"barrierAsync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_async");
dispatch_barrier_async(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_async");
    
dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierAsync end");
  • 测试结果打印:
2017-06-29 12:29:32.048 Test - 多线程[22813:835755] barrierAsync begin //①
 2017-06-29 12:29:32.049 Test - 多线程[22813:835755] before dispatch_barrier_async //②
 2017-06-29 12:29:32.049 Test - 多线程[22813:835815] ----1-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //③
 2017-06-29 12:29:32.049 Test - 多线程[22813:837436] ----2-----<NSThread: 0x600000267540>{number = 6, name = (null)} //④
 2017-06-29 12:29:32.049 Test - 多线程[22813:835755] after dispatch_barrier_async //⑤
 2017-06-29 12:29:32.049 Test - 多线程[22813:837436] ----barrier-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑥
 2017-06-29 12:29:32.049 Test - 多线程[22813:835755] barrierAsync end //⑦
 2017-06-29 12:29:32.050 Test - 多线程[22813:837436] ----3-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑧
 2017-06-29 12:29:32.050 Test - 多线程[22813:835815] ----4-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //⑨
总结:
  • 上面已经得出dispatch_barrier_async和dispatch_barrier_sync的重要区别是会不会阻塞当前线程,所以按需选择使用哪种barrier技术
  • 如果在主线程需要用到,推荐dispatch_barrier_async因为不会阻塞线程,如果在barrier之前提交的任务中有耗时任务,也不会带来主线程UI的卡顿

3.3 迭代dispatch_apply

dispatch_apply : 提交block任务块到调度队列进行多次调度.

三个参数解析:

  • iterations表示调度总次数;
  • queue任务提交的队列,如果是并发队列,那么调度可以并发执行,提高调用次数
  • (^block)(size_t),带有一个size_t参数的block块, size_t类型的参数是当前迭代索引 ; block是任务块
void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));

NOTE:

  • dispatch_apply是同步函数,会阻塞当前线程,直到迭代完毕,由③可以验证.
  • dispatch_apply可能会提高迭代速度.你可以指定串行或并发 queue,并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了
  • 如果当前队列是串行队列,而且在当前串行队列中使用dispatch_apply指定当前队列为迭代队列,会死锁
  • 并发队列中,迭代次序是不定的,有测试打印结果①②可以验证

测试代码:

NSLog(@"apply begin");
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"index = %zd , thread = %@",index,[NSThread currentThread]);
    });
    NSLog(@"apply end");

测试打印结果:

2017-06-29 14:46:08.973 Test -  多线程[24285:925046] apply begin
2017-06-29 14:46:08.973 Test -  多线程[24285:925046] index = 0 , thread = <NSThread: 0x608000079c00>{number = 1, name = main}
2017-06-29 14:46:08.973 Test -  多线程[24285:925297] index = 1 , thread = <NSThread: 0x608000268600>{number = 7, name = (null)} //①
2017-06-29 14:46:08.973 Test -  多线程[24285:925094] index = 3 , thread = <NSThread: 0x60000026cb40>{number = 5, name = (null)} //②
2017-06-29 14:46:08.973 Test -  多线程[24285:925296] index = 2 , thread = <NSThread: 0x60800026ac80>{number = 6, name = (null)}
2017-06-29 14:46:08.974 Test -  多线程[24285:925046] apply end //③

3.4 一次执行dispatch_once

dispatch_once的作用:保证其block代码块中的代码只执行一次,是线程安全的,常用于实现单例等
数据类型
  • dispatch_once_t , 其实就是long类型
typedef long dispatch_once_t;

方法,我们在使用的时候,是一个dispatch_once的宏,对应的是一个_dispatch_once的函数._dispatch_once函数两个参数,第一个predicate是标志位,第二个是代码块。

线程安全简单理解:
  • 第一次执行,block需要被调用,调用结束后需要置标记变量
  • 非第一次执行,而此时第一次执行尚未完成,线程需要等待第一次执行完成后才能继续往下执行
  • 非第一次执行,而此时#1已经完成,线程直接跳过block而进行后续任务
  • 更详细的深入探究可以看这里一步步分析dispatch_once的低负载特性
void
_dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    } else {
        dispatch_compiler_barrier();
    }
    DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

测试代码

dispatch_apply(4, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"------index = %zd",index);
        static dispatch_once_t onceToken; //①
        dispatch_once(&onceToken, ^{//②
            NSLog(@"------run");
        });
     });

测试打印结果

2017-06-29 15:11:57.542 Test -  多线程[24619:946139] ------index = 1
2017-06-29 15:11:57.542 Test -  多线程[24619:946049] ------index = 0
2017-06-29 15:11:57.542 Test -  多线程[24619:946136] ------index = 2
2017-06-29 15:11:57.542 Test -  多线程[24619:946139] ------run

3.5 延迟 dispatch_after

dispatch_after(dispatch_time_t when,
    dispatch_queue_t queue,
    dispatch_block_t block);
  • dispatch_after,延迟指定时间when后,提交block任务到队列queue。注意:并不是延时执行任务
  • 测试代码:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"run-----"); // 延迟2秒后提交任务
    });

3.6 线程状态操作: 挂起和恢复队列

  • 当需要挂起队列时,使用dispatch_suspend方法;
  • 恢复队列时,使用dispatch_resume方法
注意点:
  • dispatch_suspend 并不会立即挂起队列,根据API文档可以查看到。调用dispatch_suspend函数后,当前正在执行的block任务会继续执行下面的测试打印结果①②可以验证;只是其后添加的任务会被挂起,直到调用dispatch_resume恢复队列,从下面的测试打印结果③④⑤顺序可以验证
  • dispatch_suspend 可以挂起串行、并发队列,但是不能挂起全局并发队列,下面测试代码中打开test1可以自行验证
  • dispatch_suspend 和dispatch_resume必须成对调用。可以简单理解为:dispatch_suspend会让队列的某个计数器加1,dispatch_resume会让这个计数器减1 , 只有当这个计数器为0的时候才回恢复队列
  • 以上注意点,可以从下面测试打印结果中验证。

测试代码:

-   (void)testsuplend
{
    //test1
    //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // dispatch_suspend对全局并发队列无效
    //test2
    //    dispatch_queue_t queue = dispatch_queue_create(NULL, 0); // dispatch_suspend 对串行队列有效
    //test3
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); // dispatch_suspend 对自己创建的并发队列有效
    dispatch_async(queue, ^{
        for (int i = 0 ; i < 10; i ++) {
            NSLog(@"---suplend1 --  %zd thread = %@",i,[NSThread currentThread]);
            sleep(1);
        }
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_suspend queue one , suplend1打印任务执行完毕后会挂起队列,直到dispatch_resume调用后恢复队列");
        dispatch_suspend(queue);
    });
    
    // 4s后继续添加新的任务,如果在添加之前点击挂起,此任务不会执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_async(queue, ^{
            for (int i = 0 ; i < 10; i ++) {
                sleep(2);
                NSLog(@"---suplend2 --  %zd thread = %@",i,[NSThread currentThread]);
            }
        });
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_resume queue one ,---suplend2 --打印任务会恢复执行");
        dispatch_resume(queue);
    });
}

测试打印结果:

2017-06-29 23:31:56.678 Test -  多线程[57328:8371175] ---suplend1 --   0 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:57.680 Test - 多线程[57328:8371175] ---suplend1 --   1 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:58.682 Test - 多线程[57328:8371175] ---suplend1 --   2 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:58.777 Test - 多线程[57328:8371126] dispatch_suspend queue one , suplend1打印任务执行完毕后会挂起队列,直到dispatch_resume调用后恢复队列 //①
     2017-06-29 23:31:59.687 Test - 多线程[57328:8371175] ---suplend1 --   3 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//②
     2017-06-29 23:32:00.689 Test - 多线程[57328:8371175] ---suplend1 --   4 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:01.690 Test - 多线程[57328:8371175] ---suplend1 --   5 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:02.677 Test - 多线程[57328:8371126] dispatch_resume queue one ,---suplend2 --打印任务会恢复执行//③
     2017-06-29 23:32:02.692 Test - 多线程[57328:8371175] ---suplend1 --   6 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:03.695 Test - 多线程[57328:8371175] ---suplend1 --   7 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//④
     2017-06-29 23:32:04.682 Test - 多线程[57328:8371177] ---suplend2 --   0 thread = <NSThread: 0x60000027b7c0>{number = 4, name = (null)}//⑤
     2017-06-29 23:32:04.699 Test - 多线程[57328:8371175] ---suplend1 --   8 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:05.702 Test - 多线程[57328:8371175] ---suplend1 --   9 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 582评论 0 0
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 340评论 0 0
  • 如果一个人离开你了,就不要去追了。 如果一样东西不在属于你,就放弃吧。 是你的终究会回到你身边,不是你的执着也没用
    三年玖阅读 154评论 0 0
  • 叩拜师父 一早有个妹妹发信息,说是自己没提升,找不到方向了,问我咋办。我与她关系较亲密,又视她如己出,于...
    若尹能量阅读 1,128评论 0 3
  • 《圆觉经》经典名称,意义简述 作者:「忆民-常乐我净」 日期:2017/2/14 经典名称:《大方广圆觉修多罗了义...
    憶民_常樂我淨阅读 1,047评论 0 3