GCD之函数与队列

970 阅读6分钟
  • GCD概念

    GCD是Grand Central Dispatch的简称,是Apple开发的一个多核编程的解决方法,GCD包括了语言特性、运行时库和系统改进,这项改进为并发代码在iOS和OS X的多核硬件上提供了系统全面的支持。GCD帮助系统和你的应用运行更快,更有效率,并且提高了响应速度。GCD是在系统级上操作的,可以更好地适应所有运行中的程序的需求,以平衡的方式使它们匹配可利用系统资源。
    GCD优势:
    1. GCD是苹果公司为多核的并行运算提出的解决方案
    2. GCD会自动利用更多的CPU内核(比如双核、四核)
    3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 总结
      GCD就是将任务丢到队列中并指定任务执行的函数
      注意:我们常说的并发任务也就是同时可以执行多个任务其实是个假象,并非同时执行多个任务,如果CPU是单核的那么同时只能执行一个任务,只不过,CPU任务指向在不断变换例如有是个任务并发执行底层的实现其实是按照添加顺序CPU从第一个任务开始执行然后第一个任务暂停开始指向第二个任务此时第二个任务开始执行,继续指向第三个任务,依次循环,应为中间中间产生的时间差非常低所以给人的感觉像是多个任务同时进行,这里说到的GCD会自动利用更多的CPU内核意思就是如果是双核此时同时执行的任务就是两个任务而不是一个
  • GCD的核心组成

    队列、函数、任务
    • 队列:就是需要选择任务添加的队列(串行队列、并发队列)
    • 函数:任务需要指定执行函数(异步函数、同步函数)
    • 任务:需要执行的操作 简单的GCD使用示例:
    //任务
     dispatch_block_t bloct = ^{
         NSLog(@"12334");
     };
     //队列(并发队列)
     dispatch_queue_t conque = dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT);
     //函数(异步函数)
     dispatch_async(conque, bloct);
    
  • 函数

    任务的执行需要指定对应的函数(任务是使用block封装没有任何参数和返回值可用dispatch_block_t声明一个任务),该函数分为异步函数和同步函数
    • 同步函数dispatch_sync
      1. 必须等待当前语句执行完成之后才会执行下一条语句
      2. 不会开启线程
      3. 会造成堵塞现象
      4. 在当前执行block的任务
    • 异步函数
      1. 会开启线程执行任务
        这里也不一定会开启线程执行任务,如果是串行队列则不会开辟线程(串行队列只会开辟一个线程)
      2. 不需要等待当前语句执行完毕就可以执行下一条语句
  • 队列

    GCD编程的核心就是调度队列(dispatch queue)。dispatch queue是一个FIFO(先进先出)的队列,它保证了GCD任务按照FIFO的顺序执行,即第一个添加到dispatch queue中的任务,第一个执行,第二个添加到dispatch queue中的任务第二个执行,如此直到终点。所有的dispatch queue都是线程安全的,你能从多个线程中并发的访问他们。
    • 串行队列
      使用dispatch_queue_create创建队列时第二个参数是NULL或者是DISPATCH_QUEUE_SERIAL则此时创建出来的队列就是串行队列
      每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个 串行队列.png
    • 并发队列
      使用dispatch_queue_create创建队列时第二个参数是DISPATCH_QUEUE_CONCURRENT则此时创建出来的队列就是并发队列
      一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行) 并行队列.png
    • 主队列
      main()函数执行执行之前就已经被创建的一个串行队列,一般用于刷新UI,这个函数返回的主队列与全局并发队列一样无法响应dispatch_suspend,dispatch_resume,dispatch_set_context方法,即无法执行暂停、继续及设置内容。可通过dispatch_get_main_queue();获取主队列
    • 全局并发队列
      为了⽅便程序员的使⽤,苹果提供了全局队列 dispatch_get_global_queue(0, 0) 全局队列是⼀个并发队列在使⽤多线程开发时,如果对队列没有特殊需求,在执⾏异步任务时,可以直接使⽤全局队列,可以指定执行的服务质量(函数的第一个参数即是服务质量)。dispatch_suspend,dispatch_resume,和dispatch_set_context对于这个函数返回的全局队列是无效的。 主队列全局并发队列的一般使用方式:
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //执行耗时操作
            dispatch_async(dispatch_get_main_queue(), ^{
                //回到主线程进行UI操作
            });
        });
      
  • 函数与队列的组合使用

    • 串行队列+同步函数
      不会开辟线程任务按顺序执行 image.png
    • 串行队列+异步函数
      会创建一个线程,但是任务依然是顺序执行 image.png
    • 并发队列+同步函数
      顺序执行,不会开辟线程(在主线程中执行) image.png
    • 并发队列+异步函数
      每个任务都会创建一个线程,并发执行 image.png
    • 主队列+同步函数
      会造成死锁情况,详细内容请看下文
    • 主队列+异步函数
      不会再开辟线程,顺序执行
  • 死锁现象

    队列中任务相互等待的情况会造成死锁,例如,主线程中再添加一个同步函数 image.png 例如串行队列中同步函数中再在当前队列中添加一个同步函数 image.png 死锁问题.png
  • 耗能问题

    image.png image.png 发现异步函数是要比同步函数更耗能的
  • 相关面试题

    1. 串行队列+异步函数
      - (void)textDemo2{
        // 同步队列
        dispatch_queue_t queue = dispatch_queue_create("TD", NULL);
            NSLog(@"1");
            // 异步函数
            dispatch_async(queue, ^{
                NSLog(@"2");
                // 同步
                dispatch_async(queue, ^{
                    NSLog(@"3");
                });
                 NSLog(@"4");
            });
            NSLog(@"5");
      
        }
      

      上述代码的执行顺序: 1 5 2 4 3
      题解: image.png 注意:因为打印 1 和打印 5 都是在主线程中完成的,所以如果出现主线程卡顿的情况,那么也有可能先打印 2

    2. 并发队列+异步函数+同步函数
      - (void)textDemo1{
        dispatch_queue_t queue = dispatch_queue_create("td", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        dispatch_async(queue, ^{
            NSLog(@"2");
            dispatch_sync(queue, ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");
      
      }
      

      上述代码的执行顺序: 1 5 2 3 4
      题解: image.png

    3. 并发队列+异步函数+同步函数
      - (void)wbinterDemo{//
         dispatch_queue_t queue = dispatch_queue_create("com.td.cn", DISPATCH_QUEUE_CONCURRENT);
         // 1 2 3
         //  0 (7 8 9)
         dispatch_async(queue, ^{ // 耗时
             NSLog(@"1");
         });
         dispatch_async(queue, ^{
             NSLog(@"2");
         });
      
         dispatch_sync(queue, ^{
             NSLog(@"3");
         });
      
         NSLog(@"0");
      
         dispatch_async(queue, ^{
             NSLog(@"7");
         });
         dispatch_async(queue, ^{
             NSLog(@"8");
         });
         dispatch_async(queue, ^{
             NSLog(@"9");
         });
         // A: 1230789
         // B: 1237890
         // C: 3120798
         // D: 2137890
      }
      

      上述代码的执行顺序: A C
      题解: image.png

    4. 下面代码中,队列的类型有几种
      // OS_dispatch_queue_serial
         dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
         // OS_dispatch_queue_concurrent
         // OS_dispatch_queue_concurrent
         dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
         // DISPATCH_QUEUE_SERIAL max && 1
         // queue 对象 alloc init class
         dispatch_queue_t mainQueue = dispatch_get_main_queue();
      
         // 多个 - 集合
         dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
      

      一般人会觉得第一个是串行队列,第二个是并发队列,第三个是主队列,第四个是全局并发队列一共四个,其实是两个,主队列其实就是串行队列,全局并发队列其实就是并发队列,所以只有两种类型串行队列和并发队列