阅读 185

GCD 之 函数与队列

GCD全称Grand Central Dispatch,基于C语言的函数,会自动利用更多的CPU内核自动管理线程生命周期

总结: GCD就是将任务添加到队列,并指定任务执行的函数

函数

GCD中有两种执行任务的方式:同步函数(dispatch_sync)异步函数(dispatch_async)

同步函数(dispatch_sync)

  • 必须等待当前语句执行完毕,才会执行下一条语句,会阻塞当前线程

  • 不会开辟新的线程

异步函数(dispatch_async)

  • 不必等待当前语句执行完毕,就可以执行下一条语句

  • 具有开辟新线程的能力,但不一定会开辟新线程,与当前任务所指定的队列类型相关

队列

队列(dispatch queue)是一种数据结构,特殊的线性表,是用来存放任务的队列,遵循FIFO(先进先出)原则,新任务总是被插入到队尾,任务从队首开始读取,每读取一个任务,该任务就会从队列总释放,

串行队列

  • 同一时刻只能执行一个任务

  • dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);创建串行队列

  • DISPATCH_QUEUE_SERIAL也可以用NULL代替

// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.CJL.Queue", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_SERIAL);复制代码

image.png

并发队列

  • 同一时刻可以执行多个任务

  • dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列

  • 只有在异步函数下才有并发效果

// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);复制代码

image.png

主队列

主队列(Main Dispatch Queue)

  • 特殊的串行队列

  • 专门用来在主线程上调度任务的串行队列,依赖于主程序、主Runloop,在main函数之前调用

  • dispatch_get_main_queue()获取主队列

//主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();复制代码

全局队列

全局队列(Global Dispatch Queue)

  • GCD默认的并发队列

  • dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);获取全局并发队列

    • 第一个参数表示队列优先级,默认DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在iOS9.0后被服务质量quality-of-service取代

    • 第二个参数使用0

//全局并发队列的获取方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

//优先级从高到低(对应的服务质量)依次为
- DISPATCH_QUEUE_PRIORITY_HIGH       -- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT    -- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW        -- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND复制代码

在日常开发中,全局队列+并发并列一般是这样配合使用的

//主队列 + 全局并发队列的日常使用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //执行耗时操作
    dispatch_async(dispatch_get_main_queue(), ^{
        //回到主线程进行UI操作
    });
 });复制代码

串行队列 + 同步函数

【任务按顺序执行】:任务一个接一个的在当前线程执行,不会开辟新线程

串行队列 + 异步函数

【任务按顺序执行】:任务一个接一个的执行,会开辟新线程

并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟线程

并发队列 + 异步函数

【任务乱序执行】:任务执行无顺序,会开辟新线程

主队列 + 同步函数

【造成死锁】:任务相互等待,造成死锁

死锁现象

  • 主线程因为你同步函数的原因等着先执行任务

  • 主队列等着主线程的任务执行完毕再执行自己的任务

  • 主队列和主线程相互等待会造成死锁

主队列 + 异步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟线程

全局并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟新线程

全局并发队列 + 异步函数

【任务乱序执行】:任务乱序执行,会开辟新线程

总结

函数\队列串行队列并发队列主队列全局并发队列
同步函数顺序执行,不开辟线程顺序执行,不开辟线程死锁顺序执行,不开辟线程
异步函数顺序执行,开辟线程乱序执行,开辟线程顺序执行,不开辟线程乱序执行,开辟线程

相关面试题

【面试题1】异步函数+并发队列

- (void)interview01{
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
----------打印结果-----------
输出顺序为:1 5 2 4 3复制代码

异步函数会开辟新线程,不会阻塞主队列,

分析
  • 主队列的任务是NSLog(1)、异步Block、NSLog(5),因为NSLog(1)NSLog(5)的复杂度是一样的,而异步Block的复杂度更高,所以NSLog(1)和NSLog(5)优先于异步Block

  • 异步Block中同理,NSLog(2)和NSLog(4)优先于异步Block

  • 主线程阻塞或其他极端情况下,NSLog(2)有可能优先NSLog(1)NSLog(5)

【面试题2】异步函数嵌套 同步函数+并发队列

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

----------打印结果-----------
输出顺序为:1 5 2 3 4复制代码
分析
  • 任务1和任务5的分析和前面面试一样,所以执行顺序是任务1、任务5、异步block

  • 在异步block中首先执行任务2,因为同步函数会阻塞线程,所以执行顺序是任务2、任务3、任务4

【面试题3】异步串行嵌套同步串行

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

----------打印结果-----------
输出顺序为:1 5 2 死锁崩溃复制代码
分析
  • 任务1、任务5、异步block的执行顺序和上面分析的一样

  • 异步block中,首先将任务2同步block任务4添加到串行队列,等待执行,串行队列中的任务顺序任务2 --> 同步block --> 任务4

  • 开始执行时,先执行任务2,再执行同步Block,因为是同一个串行队列,所以会将任务3添加在任务4的后面,串行队列中的任务顺序同步block --> 任务4 -->任务3

  • 因为同步函数会阻塞当前线程,所以任务4等待同步block执行完毕,但是当前又是串行队列,遵循先进先出原则,所以任务3等待任务4,造成死锁

  • 死锁会有一个关键的堆栈信息_dispatch_sync_f_slow

【面试4】异步函数+同步函数+并发队列

下面代码的执行顺序是什么?(答案是 AC)
A: 1230789
B: 1237890
C: 3120798
D: 2137890

- (void)interview04{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);

    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");
    });
}

----------打印结果-----------
输出顺序为:(1 2 3 无序)0(7 8 9 无序),可以确定的是 0 一定在3之后,在789之前复制代码
分析
  • 因为任务1、任务2是异步并发,会开辟新线程,所以没有固定顺序,

  • 同理,任务7、任务8、任务9也没有固定顺序

  • 因为任务3是同步并发,会阻塞当前线程,所以任务3任务0之前执行,所以任务0会在任务3之后,任务7、8、9之前

【面试题5】下面队列有几种类型

/串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.CJL.Queue", NULL);
    
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);复制代码

队列只有两种:

  • 并发队列:并发队列(DISPATCH_QUEUE_CONCURRENT)全局并发队列(dispatch_get_global_queue)

  • 串行队列:串行队列(DISPATCH_QUEUE_SERIAL \ NULL)串行主队列(dispatch_get_main_queue)



作者:Study_Min
链接:https://juejin.cn/post/7016617855751815182


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐