ios多线程

2018-07-27  本文已影响11人  edison0428

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

基本概念

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

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

优点:

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

缺点

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

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

总结

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

image.png

NSOperation & NSOperationQueue

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

GCD

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

同步任务(sync)和异步任务(async)的主要区别在于会不会阻塞当前线程,直到block中的任务执行完毕
同步任务(sync):它会阻塞当前线程并等待block中的任务执行完毕,然后当前线程才会继续往下运行,同步任务gcd不会去线程池去拿线程
异步任务(async):当前线程会直接往下执行,并不会阻塞当前线程,只有还有异步任务gcd回去线程池拿线程(主队列除外)

/**
 异步任务 + 主队列
 
  结果一定是 1 3 2
 */
-(void)asyncMainTask{

    NSLog(@"11111");
    dispatch_async(dispatch_get_main_queue(), ^{
        //新的任务 
        NSLog(@"22222");
    });
    [NSThread sleepForTimeInterval:2];
    NSLog(@"33333");
    
}

/**
 异步任务 + 串行队列
 
 1 2 3  或者  1 3 2 不一定
 */
-(void)asyncSerialTask{
    

    NSLog(@"11111");
    dispatch_queue_t cusQueue = dispatch_queue_create("myQueue", NULL);
    dispatch_async(cusQueue, ^{
        
        NSLog(@"22222");
    });
    NSLog(@"33333");
}

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

image.png

可以看到
同步方法不一定在本线程,同步不会开启新的线程
要想开启线程必须异步执行
但是异步方法方法也不一定新开线程(考虑主队列)。

并发队列同步执行 和 串行队列同步执行的效果一样

这样就不会开启新的线程
 dispatch_async(dispatch_get_main_queue(), ^{
        
    });

异步通常就是多线程的代名词

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锁一样的死锁问题

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一些常用的方法,还有一些不常用也没去做记录了

上一篇下一篇

猜你喜欢

热点阅读