美文网首页
ios多线程

ios多线程

作者: edison0428 | 来源:发表于2018-07-27 22:46 被阅读11次

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

    基本概念

    • 进程

    进程是指系统中正在运行的一个应用程序,针对于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),然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看

    • NSOperation的两个子类

      1.NSInvocationOperation

      2.NSBlockOperation

    GCD

    • 同步任务与异步任务

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

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

    • 队列

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

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

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

    相关文章

      网友评论

          本文标题:ios多线程

          本文链接:https://www.haomeiwen.com/subject/ofrnmftx.html