ios多线程

养成好习惯,把学过的东西都留一手,如有错请指示

基本概念

  • 进程

进程是指系统中正在运行的一个应用程序,针对于iOS来说,就是开启了一个app,这个app的运行就是一个进程

  • 线程

一个进程要想执行任务,那么就必须得有线程,一个进程至少得有一个线程

  • 多线程的优缺点

优点:

1.能适当的提高程序的运行效率
2.能适当的提高cpu的利用率

缺点

1.开启线程需要栈和寄存器等内存消耗,默认的一条线程占用栈去512kb
2.线程过多会导致CPU在线程上的消耗比较大
3.线程过多,程序设计就复杂了

  • 并发与并行

    并发

    并发:描述的是多个任务同时发生,都需要被处理,这是一种现象,侧重点在发生

    并行

并行:指的是一种技术,一个同时处理多个任务的技术,侧重点在运行

总结

我们说的多线程,其实就是采取了并行技术,从而提高执行效率,因为有个多个线程,所以计算机的多个cpu可以同时工作,处理不同线程内的指令,但是对于单核的cpu而言,多线程其实cpu在多个线程不停的调度,并发是一种现象,面对这一现象,我们首先创建多个线程,真正加快程序运行速度的是并行技术,也就是让多个cpu同时工作,而多线程是为了让cpu同时工作成为可能,而对于单核的cpu,就是让cpu能在各个线程调度称为可能

NSOperation & NSOperationQueue

简介:把要执行的任务封装到一个操作对象(operation object),并将操作对象放入操作队列(nsoperationqueue),然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看

  • NSOperation的两个子类

    1.NSInvocationOperation

    2.NSBlockOperation

GCD

  • 同步任务与异步任务

任务:即操作,在gcd当中就是一个block,任务执行的两种方式:同步执行 和 异步执行

同步任务(sync)和异步任务(async)的主要区别在于会不会阻塞当前线程,直到block中的任务执行完毕
同步任务(sync):它会阻塞当前线程并等待block中的任务执行完毕,然后当前线程才会继续往下运行
异步任务(async):当前线程会直接往下执行,不用等着当前任务执行完,并不会阻塞当前线程

同步:只能在当前线程执行任务,不具备开启新线程的能力
异步:可以在新的线程执行任务,具备开启新线程的能力

  • 队列

    • 串行队列:先进先出队列,每次只执行一个任务,线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)
  • 并发队列:先进先出队列,不过可以形成多个任务并发,也就是说,虽然也是FIFO,但是不同的是,它取出来一个任务就放到别的线程,然后再取出来一个放到别的线程,动作很快,看起来所有的任务都是一起执行的,不过gcd会根据系统资源控制并行的数量,多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的
  • 主队列:这个是一个特殊的串行队列,队列中的每个任务一定执行在主线程中,如果主线程上有任务在执行,主队列就不会调度任务
  • 总结

关于同步异步、串行并行和线程的关系,下面通过一个表格来总结

屏幕快照 2020-02-18 下午12.27.47.png

可以看到,同步方法不一定在本线程,异步方法方法也不一定新开线程(考虑主队列)。

  • 开不开线程,取决于执行任务的函数,同步不开,异步才能开
  • 开几条线程,取决于队列,串行开一条,并发可以开多条(异步)

队列是负责调度任务的,同步异步负责执行任务
串行队列和并发队列的区别:会不会阻塞当前队列(阻塞的意思:队列里的任务不用执行完,就可以拿出下一个任务)
同步和异步的区别:会不会阻塞当前线程(阻塞的意思:不用等待当前的任务是否完成,就可以执行下一个任务)


image.png

串行队列 并发队列 demo

-(void)gcdDemo8{
    /*
     全局队列 & 并发队列
     1> 名称,并发队列取名字,适合于企业开发跟踪错误
     2> release,在MRC 并发队列 需要使用的
        dispatch_release(q);//ARC 情况下不需要release !
     
     
     全局队列 & 串行队列
        全局队列: 并发,能够调度多个线程,执行效率高
            - 费电
        串行队列:一个一个执行,执行效率低
            - 省点
     
        判断依据:用户上网方式
            - WIFI : 可以多开线程
            - 流量  : 尽量少开线程
     
     */
    //1.队列
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i< 10; i++) {
        NSLog(@"  %d",i);
        dispatch_async(q, ^{
            NSLog(@"%@  %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");
   
    
}



//MARK:  全局队列 (本质上并发队列)
-(void)gcdDemo7{
    //全局队列
    /* 参数
     1. 涉及到系统适配
     iOS 8   服务质量
     QOS_CLASS_USER_INTERACTIVE    用户交互(希望线程快速被执行,不要用好使的操作)
     QOS_CLASS_USER_INITIATED      用户需要的(同样不要使用耗时操作)
     QOS_CLASS_DEFAULT             默认的(给系统来重置队列的)
     QOS_CLASS_UTILITY             使用工具(用来做耗时操作)
     QOS_CLASS_BACKGROUND          后台
     QOS_CLASS_UNSPECIFIED         没有指定优先级
     iOS 7  调度的优先级
     - DISPATCH_QUEUE_PRIORITY_HIGH 2               高优先级
     - DISPATCH_QUEUE_PRIORITY_DEFAULT 0            默认优先级
     - DISPATCH_QUEUE_PRIORITY_LOW (-2)             低优先级
     - DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
     
     提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
     
     
     2. 为未来使用的一个保留,现在始终给0.
     
     老项目中,一般还是没有淘汰iOS 7  ,没法使用服务质量
     */
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i< 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@  %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");
}


#pragma mark - <同步任务>

//MARK : 增强版同步任务
// 可以队列调度多个任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这就是依赖关系
// - 同步任务,会造成一个死锁!
-(void)gcdDemo6{
    //队里
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    //任务
    void (^task)()=^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d   %@",i ,[NSThread currentThread]);
            if (i==5) {
                //1.用户登录
                dispatch_sync(q, ^{
                    for (int i = 0; i < 5; i++) {
                        NSLog(@"用户登录  %@",[NSThread currentThread]);
                    }
                });
            }
        }
        //2.支付
        dispatch_async(q, ^{
            NSLog(@"支付  %@",[NSThread currentThread]);
        });
        
        //3.下载
        dispatch_async(q, ^{
            NSLog(@"下载  %@",[NSThread currentThread]);
        });
   
    };
    dispatch_async(q, task);
//    NSLog(@"come here");
    
}

//MARK - 同步任务作用!
/**
 在开发中,通常会将耗时操作放后台执行,有的时候,有些任务彼此有"依赖"关系!
 
 例子: 登录,支付,下载
 
 利用同步任务,能够做到任务依赖关系,前一个任务是同步任务,哥么不执行完,队列就不会调度后面的任务
 */
-(void)gcdDemo5{
    dispatch_queue_t loginQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    //1.用户登录
    dispatch_sync(loginQueue, ^{
        NSLog(@"用户登录  %@",[NSThread currentThread]);
    });
    //2.支付
    dispatch_async(loginQueue, ^{
        NSLog(@"支付  %@",[NSThread currentThread]);
    });
    //3.下载
    dispatch_async(loginQueue, ^{
        NSLog(@"下载  %@",[NSThread currentThread]);
    });
    for (int i = 0; i< 10; i++) {
        NSLog(@"......%@",[NSThread currentThread]);
    }
    
    
}





//MARK : 并发队列,同步执行   和 串行队列,同步执行 效果一样!
-(void)gcdDemo4{
    
    // 会开线程吗?  顺序执行?  come here?
    //  不会          顺序     最后
    
    //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    //2.同步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}


//MARK : 并发队列,异步执行
-(void)gcdDemo3{
    //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    //2.异步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}



//MARK: 串行队列,异步任务
-(void)gcdDemo2{
    /**
     会开几条线程?会顺序执行吗?
     */
    //1.队列 - 串行
    dispatch_queue_t q = dispatch_queue_create("test", NULL);
    
    //2.异步执行任务
    for (int i = 0; i < 10; i++) {
        NSLog(@"%d------------",i);
        dispatch_async(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}


//MARK:串行队列,同步任务
/**
*   不会开启线程,会顺序执行
*/
-(void)gcdDemo1{
    //1.队列 - 串行
    
    /**
     1.队列名称:
     2.队列的属性: DISPATCH_QUEUE_SERIAL 标示串行!
     */
    dispatch_queue_t q = dispatch_queue_create("test", NULL);
    
    //2.同步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
}

主队列


重点:主队列,以FIFO调度任务,如果主线程上有任务在执行,主队列就不会调度任务,如下代码能体现
    - 主要是负责在主线程上执行任务
- (void)gcdDemo1 {
    //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
    
    
    //1.队列 --> 已启动主线程,就可以获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    
    //2.异步任务
    dispatch_async(q, ^{
        NSLog(@"111==%@",[NSThread currentThread]);
    });
    NSLog(@"come here");
    NSLog(@"come here1");
    NSLog(@"come here2");
    NSLog(@"come here3");
    
    
    dispatch_async(q, ^{
        NSLog(@"222==%@",[NSThread currentThread]);
    });
    [self test];
    
    /*
    这么理解哈,整个gcdDemo1e也相当于任务在主队列里执行
     test 方法就一个耗时操作
     打印顺序是
     come here
     come here1
     come here2
     come here3
     test的for循环打印
     3333 (3333是touch方法调用gcdDemo1e后的一个打印语句)
     111==
     222==
如果主线程上有任务在执行,主队列就不会调度任务
     所以在主队列里异步执行任务,就要等主队列的其他任务都执行完了,在执行block的任务
     
     */
    
}


- (void)gcdDemo2 {
    //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
    NSLog(@"这里!!");
    //1.队列 --> 已启动主线程,就可以获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    
    //2.同步任务  ==> 死锁
    dispatch_sync(q, ^{
        NSLog(@"能来吗? ");
    });
    NSLog(@"come here");
    
    /*
     为什么是死锁呢
     跟gcdDemo1的解释一样
     
     整个gcdDemo2 包括gcdDemo2后面的都相当于一个任务在主线程里执行,
     所以必须要等这个任务都执行完了再去执行dispatch_sync的任务
     但是dispatch_sync又是阻塞当前线程的,也就是说dispatch_sync下面的代码要等到dispatch_sync里面的任务执行完再去执行,这不就相互等待,谁也等不到谁,所以就死锁了
     */
    
}
  • GCD死锁

1.死锁1
    NSLog(@"111111");
    dispatch_sync(dispatch_get_main_queue(), ^{
       
        
        NSLog(@"2222222");
    });
    NSLog(@"3333333");

出现的打印现象:只打印了“111111”,并且主线程已卡死,点击啊什么的都没有效果了,这个就是传说中死锁,但是在xcode8之后死锁直接报错,如下所示

Paste_Image.png

解释:同步任务会阻塞当前线程,然后把block中的任务,只有等到block中的任务完成后才会让当前线程继续往下走。这段代码的步骤:打印完第一句后,dispatch_sync 立即阻塞当前的主线程,这里关于死锁的描述有些简书模糊,“然后把 Block 中的任务放到 main_queue 中,可是 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成”,这里“main_queue 中的任务会被取出来放到主线程中执行”描述不准确,应该是main_queue中已经有任务在执行了,而这个任务就包含了同步任务,而同步任务中的block却被放到了列队底部,由于同步任务需要阻塞当前线程,完成block才能继续执行,而队列又无法弹出该block来执行,因为这时候在队列顶部的是包含了block的任务,形成了循环依赖

2.死锁2
dispatch_queue_t cusQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"111111");
    dispatch_async(cusQueue, ^{
        
        NSLog(@"2222222");
        dispatch_sync(cusQueue, ^{
            NSLog(@"3333333");
        });
        NSLog(@"44444444");
    });
    NSLog(@"5555555");

出现的打印现象:

Paste_Image.png

解释:
1.创建的cusQueue是serial,这个是串行队列
2.打印出1111
3.dispatch_async是异步执行,所以当前线程不会阻塞,于是有了两条线程这是并行的,那么22222和5555打印的先后是不确定的
4.注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就高兴的把自己 Block 中的任务放到 queue 中,可谁想 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。

3.之前误以为是死锁

    dispatch_queue_t myQueue =dispatch_queue_create("myQueue", NULL);
    NSLog(@"111111   :%d",[NSThread isMainThread]);
    dispatch_sync(myQueue, ^{
        NSLog(@"3333333  :%d",[NSThread isMainThread]);
       // dispatch_sync(myQueue, ^{
        //    NSLog(@"44444  :%d",[NSThread //isMainThread]);
    //    });
    });
    
    NSLog(@"5555555  :%d",[NSThread isMainThread]);


之前认为,同步任务 + 串行队列 就是死锁,因为之前认为同步任务会阻塞了当前线程,而串行队列的任务被取出来会在当前线程执行,所以就会被死锁,这个例子根第一个死锁很相似,区别就是第一个例子是主队列,而这个例子是串行队列。那么现在开始解释这个为什么不是死锁
1.第一句代码是创建一个串行队列
2.第二句代码的1111能打印出来这个毋庸置疑
3.重点来了,第三句代码,同步任务会阻塞当前线程,这个也是肯定的,把任务放入这个自己创建的串行队列,并且这个队列之前是没有任务的,所以代码会执行完这个block里的代码再返回继续走之后的代码,
4.所以顺序是 111 333 555,并不会死锁
5.而第一例子之所以会造成死锁,那是因为把任务放入的是主队了,而执行sync那句代码也是在主队列中,执行sync时线程已经阻塞,再把block的任务取出来是必须要等sync返回才能执行,因为sync是比block先入队列,出队列是先进的先出,所以相互等待就是死锁
6.如果这个例子,把注释掉的代码打开,那么也是死锁,因为第二个sync是串行队列的第一个任务,而block是串行队列的第二个任务,于是又是相互等待造成死锁

4.死锁总结

死锁的原因不是线程阻塞,而是队列阻塞
如果dispatch_sync()的目标queue为当前queue,会发生死锁(并行queue并不会)。使用dispatch_sync()会遇到跟我们在pthread中使用mutex锁一样的死锁问题

  • GCD常用方法

    dispatch_after
      NSLog(@"111111");  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          NSLog(@"22222");
      });
      
      dispatch_time_t  delayTime = dispatch_time(DISPATCH_TIME_NOW, 3);
      dispatch_after(delayTime, dispatch_get_main_queue(), ^{
         
          NSLog(@"33333");
      });
    

    dispatch_after只是延时提交block,并不是延时后立即执行的,dispatch_after不是很精确

    dispatch_apply
dispatch_queue_t cusQueue = dispatch_queue_create("cus", DISPATCH_QUEUE_CONCURRENT);
   
   //第一个参数,3--block执行的次数
   //第二个参数,applyQueue--block任务提交到的队列
   //第三个参数,block--需要重复执行的任务
   dispatch_apply(3, cusQueue, ^(size_t index) {
       NSLog(@"current index %@",@(index));
       sleep(1);
   });
   NSLog(@"2222");

dispatch_apply:把一项任务放入队列中多次执行,串行队列和并行队列都行,它是同步执行的函数,不会立刻返回,要等待block中的任务全部执行完才返回

dispatch_apply的正确使用方法:为了不阻塞主线程,一般把dispatch_apply放入异步队列中调用,然后执行完后通知主线程

dispatch_once

保证app在运行期间,block中的代码只执行一次,个人用的最多的就是单例的使用中

dispatch group

有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且某种特定操作必须在两个请求都结束(成功或失败)的时候才会执行,最low的办法第二个请求嵌套在第一个请求结果后在发送,在第二个请求结束后再执行操作。还有就是只使用一个Serial Dispatch Queue,把想要执行的操作全部追加到这个Serial Dispatch Queue中并在最后追加某种特定操作,颇为复杂操作。但是呢,我们这里介绍更高级的办法使用dispatch group


dispatch_queue_t cusConQueue = dispatch_queue_create("cusConQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_t cusGroup = dispatch_group_create();
    
    dispatch_group_async(cusGroup, cusConQueue, ^{
        
        NSLog(@"执行任务1");
    });
    
    dispatch_group_async(cusGroup, cusConQueue, ^{
        
        NSLog(@"执行任务2");
    });
    
    dispatch_group_async(cusGroup, cusConQueue, ^{
        
        NSLog(@"执行任务3");
    });
    
    dispatch_group_notify(cusGroup, cusConQueue, ^{
       
        NSLog(@"执行所有任务后想要的操作");
    });
    NSLog(@"44444----");

上图中的 1 2 3 4执行的顺序都不一定,因为他们都是异步,1 2 3任务都执行完成后才会执行 notif里的任务

上面的dispatch_group_notify还可以换成dispatch_group_wait,代码如下

dispatch_queue_t cusConQueue = dispatch_queue_create("cusConQueue", DISPATCH_QUEUE_CONCURRENT);
  
  dispatch_group_t cusGroup = dispatch_group_create();
  
  dispatch_group_async(cusGroup, cusConQueue, ^{
      
      NSLog(@"执行任务1");
  });
  
  dispatch_group_async(cusGroup, cusConQueue, ^{
      
      NSLog(@"执行任务2");
  });
  
  dispatch_group_async(cusGroup, cusConQueue, ^{
      [NSThread sleepForTimeInterval:2];
      NSLog(@"执行任务3");
  });

  dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1);
  
  long result=dispatch_group_wait(cusGroup, DISPATCH_TIME_FOREVER);
  
  if (result==0) {
      NSLog(@"任务执行完成");
  }
  else{
      NSLog(@"任务执行还在继续");
  }
  NSLog(@"44444----");


dispatch_group_wait第二个参数指定为等待的时间(超时),属于dispatch_time_t类型,在这里使用DISPATCH_TIME_FOREVER,意味着永久等待。如果dispatch group的处理尚未结束,就会一直等待,它会阻塞线程,所以不会放在主线程里执行,所以如果group的任务没有处理完,代码是不会执行dispatch_group_wait之后的代码,所以这里的打印 1 2 3 是无序,但是4一定是在最后

但是呢上面这种dispatch_group的排列执行方式,是不会考虑block块内部的异步请求情况的,它只能保证把block内的非异步直观代码执行完,所以如果ABC三个任务中如果有执行异步的请求,那么在dispatch_group_notify最终任务执行中,那个异步请求不一定毁掉结束,所以又给应该介绍新的api

dispatch_group_enter/dispatch_group_leave

调用dispatch_group_enter这个方法标志着一个代码块被加入了group,和dispatch_group_async功能类似;
需要和dispatch_group_enter()、dispatch_group_leave()成对出现;编译器会强制识别当出现dispatch_group_leave全部结束才执行dispatch_group_notify


dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(5);
        NSLog(@"任务一完成");
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(8);
        NSLog(@"任务二完成");
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务完成");
    });
dispatch_barrier_async

这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2");
    });
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });

如上都是GCD一些常用的方法,还有一些不常用也没去做记录了

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

推荐阅读更多精彩内容

  • 学习多线程,转载两篇大神的帖子,留着以后回顾!第一篇:关于iOS多线程,你看我就够了 第二篇:GCD使用经验与技巧...
    John_LS阅读 604评论 0 3
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,016评论 0 4
  • iOS 多线程系列 -- 基础概述iOS 多线程系列 -- pthreadiOS 多线程系列 -- NSThrea...
    shannoon阅读 829评论 0 2
  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 600评论 0 0
  • 一、前言 上一篇文章iOS多线程浅汇-原理篇中整理了一些有关多线程的基本概念。本篇博文介绍的是iOS中常用的几个多...
    nuclear阅读 2,043评论 6 18