OC底层原理26:GCD之函数与队列

张建 lol

GCD简介

  1. 什么是GCD?

全称是 Grand Central Dispatch,纯 C 语言,提供了非常强大的函数

2、GCD 的优势

  • GCD 是苹果公司为多核的并行运算提出的解决方案
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

【重点】用一句话总结 GCD : 将任务添加到队列,并且指定执行任务的函数。

GCD 核心

在日常开发中,GCD一般写成下面的形式

1
2
3
diaptch_async(dispatch_queue_create("com.zj.Queue",NULL)), ^{
NSLog(@"GCD基本使用")
});

将上述代码拆分,方便我们来理解 GCD核心,主要是由 任务 + 队列 + 函数 构成

1
2
3
4
5
6
7
8
9
//*****GCD基础写法*****
// 创建任务
dispatch_block_t block= ^{
NSLog(@"hello GCD");
}
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("com.zj.Queue",NULL);
// 将任务添加到队列,并指定函数执行
dispatch_async(queue,block);
  • dispatch_block_t:创建任务
  • dispatch_queue_t:创建队列
  • dispatch_async:函数

函数和队列

1、在GCD中函数按照执行方式分为 同步函数异步函数

  • 同步函数:dispatch_sync

    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程,即不具备开启新线程的能力
    • 在当前执行 block 任务
  • 异步函数:dispatch_async

    • 会开启新的线程执行 block 任务
    • 异步是多线程的代名词

综上所述,两种执行方式的 主要区别 有两点:

  • 是否等待 队列的任务执行完毕
  • 是否具备开启新线程 的能力

2、队列

在GCD中队列是用来 存放任务,是一种特殊的 线性表,遵循 FIFO(先进先出) 原则,新任务总是被 插入到队尾,而任务的读取是从 队首开始读取,每读一个任务,则队列中释放一个任务,如下图所示:

队列主要分为 串行队列(Serial Dispatch Queue)并发队列(Concurrent Dispatch Queue) 两种,如下图所示

  • 串行队列:每次 只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行)

    • 使用 dispatch_queue_create("xxx",DISPATCH_QUEUE_SERIAL) 创建串行队列
    • 其中 DISPATCH_QUEUE_SERIAL 也可以用 NULL 表示,这两种均表示 默认的串行队列
1
2
3
4
// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.ZJ.Queue",DISPATCH_QUQUQ_SERIAL);

dispatch_quque_t serialQuquq2 = dispatch_ququq_create("com.ZJ.Queue",Null);
  • 并发队列:一次可以 并发执行多个任务,即 开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行)

    • 使用 dispach_queue_create("xxx",DISPATCH_QUQUQ_CONCURRENT) 创建并发队列
    • 注意:并发队列 的并发功能 只有在异步函数下才有效
1
2
// 并发队列的获取方法
dispatch_quque_t concurrentQueue = dispatch_quque_create("com.ZJ.Queue",DISPATCH_QUQUE_CONCURRENT);

主队列 和 全局并发队列

在GCD中,针对这两种队列,分别提供了 主队列(Main Dispatch Queue)全局并发队列(Global Dispatch Queue)

  • 主队列(Main Dispatch Queue):GCD 中提供的 特殊的串行队列

    • 专门用来 在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前 自动创建
    • 不会开启线程
    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调用
    • 使用 dispatch_get_main_queue() 获得 主队列
    • 通常在返回 主线程 更新UI 时使用
1
2
// 主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 全局并发队列(Global Dispatch Queue):GCD 提供的默认的 并发队列

    • 为了方便程序员使用,苹果提供了全局队列
    • 在使用多线程开发时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列
    • 使用 dispatch_get_global_queue 获取全局并发队列,最简单的是 dispatch_get_global_queue(0,0)
      • 第一个参数表示 队列优先级,默认优先级未 DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在iOS9之后,已经被 服务质量(quality-of-service) 取代
      • 第二个参数使用 0
1
2
3
4
5
6
7
8
// 全集并发队列的获取方法
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

全局并发队列 + 主队列 配合使用

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

1
2
3
4
5
6
7
// 主队列 + 全局并发队列 的日常使用
dispatch_async(dispatch_get_global_queue(0,0), ^{
// 执行耗时操作
dispatch_async(dispatch_get_main_queue(),^{
// 回到主线程进行UI操作
});
});

函数与队列的不同组合

  1. 串行队列 + 同步函数

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

1
2
3
4
5
6
7
8
9
10
// 串行队列 + 同步函数
- (void)test1{
NSLog(@"串行 + 同步:%@",[NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 5; i ++) {
dispatch_sync(serialQueue, ^{
NSLog(@"串行 + 同步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 15:13:39.595134+0800 GCD之函数和队列[4500:155055] 串行 + 同步:<_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
2022-01-12 15:13:39.595275+0800 GCD之函数和队列[4500:155055] 串行 + 同步:0 - <_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
2022-01-12 15:13:39.595388+0800 GCD之函数和队列[4500:155055] 串行 + 同步:1 - <_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
2022-01-12 15:13:39.595512+0800 GCD之函数和队列[4500:155055] 串行 + 同步:2 - <_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
2022-01-12 15:13:39.595639+0800 GCD之函数和队列[4500:155055] 串行 + 同步:3 - <_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
2022-01-12 15:13:39.595759+0800 GCD之函数和队列[4500:155055] 串行 + 同步:4 - <_NSMainThread: 0x6000025d00c0>{number = 1, name = main}
  1. 串行队列 + 异步函数

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

1
2
3
4
5
6
7
8
9
10
// 串行队列 + 异步函数
- (void)test2{
NSLog(@"串行 + 异步:%@",[NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("com.ZJ.Queue", NULL);
for (int i = 0; i < 5; i ++) {
dispatch_async(serialQueue, ^{
NSLog(@"串行 + 异步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 15:20:12.884840+0800 GCD之函数和队列[4590:160591] 串行 + 异步:<_NSMainThread: 0x600000ee4140>{number = 1, name = main}
2022-01-12 15:20:12.885010+0800 GCD之函数和队列[4590:160734] 串行 + 异步:0 - <NSThread: 0x600000eb01c0>{number = 3, name = (null)}
2022-01-12 15:20:12.885136+0800 GCD之函数和队列[4590:160734] 串行 + 异步:1 - <NSThread: 0x600000eb01c0>{number = 3, name = (null)}
2022-01-12 15:20:12.885262+0800 GCD之函数和队列[4590:160734] 串行 + 异步:2 - <NSThread: 0x600000eb01c0>{number = 3, name = (null)}
2022-01-12 15:20:12.885392+0800 GCD之函数和队列[4590:160734] 串行 + 异步:3 - <NSThread: 0x600000eb01c0>{number = 3, name = (null)}
2022-01-12 15:20:12.885538+0800 GCD之函数和队列[4590:160734] 串行 + 异步:4 - <NSThread: 0x600000eb01c0>{number = 3, name = (null)}
  1. 并发队列 + 同步函数

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

1
2
3
4
5
6
7
8
9
10
// 并发队列 + 同步函数
- (void)test3{
NSLog(@"并发 + 同步:%@",[NSThread currentThread]);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5; i ++) {
dispatch_sync(serialQueue, ^{
NSLog(@"并发 + 同步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
22-01-12 15:23:15.939687+0800 GCD之函数和队列[4630:163688] 并发 + 同步:<_NSMainThread: 0x600000de8000>{number = 1, name = main}
2022-01-12 15:23:15.939830+0800 GCD之函数和队列[4630:163688] 并发 + 同步:0 - <_NSMainThread: 0x600000de8000>{number = 1, name = main}
2022-01-12 15:23:15.939922+0800 GCD之函数和队列[4630:163688] 并发 + 同步:1 - <_NSMainThread: 0x600000de8000>{number = 1, name = main}
2022-01-12 15:23:15.940014+0800 GCD之函数和队列[4630:163688] 并发 + 同步:2 - <_NSMainThread: 0x600000de8000>{number = 1, name = main}
2022-01-12 15:23:15.940102+0800 GCD之函数和队列[4630:163688] 并发 + 同步:3 - <_NSMainThread: 0x600000de8000>{number = 1, name = main}
2022-01-12 15:23:15.940217+0800 GCD之函数和队列[4630:163688] 并发 + 同步:4 - <_NSMainThread: 0x600000de8000>{number = 1, name = main}
  1. 并发队列 + 异步函数

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

1
2
3
4
5
6
7
8
9
10
// 并发队列 + 异步函数
- (void)test4{
NSLog(@"并发 + 异步:%@",[NSThread currentThread]);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5; i ++) {
dispatch_async(serialQueue, ^{
NSLog(@"并发 + 异步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 15:26:35.249949+0800 GCD之函数和队列[4716:166604] 并发 + 异步:<_NSMainThread: 0x600003344740>{number = 1, name = main}
2022-01-12 15:26:35.250144+0800 GCD之函数和队列[4716:166729] 并发 + 异步:0 - <NSThread: 0x60000332c100>{number = 7, name = (null)}
2022-01-12 15:26:35.250153+0800 GCD之函数和队列[4716:166735] 并发 + 异步:1 - <NSThread: 0x600003300d40>{number = 6, name = (null)}
2022-01-12 15:26:35.250174+0800 GCD之函数和队列[4716:166734] 并发 + 异步:3 - <NSThread: 0x60000330f0c0>{number = 4, name = (null)}
2022-01-12 15:26:35.250238+0800 GCD之函数和队列[4716:166731] 并发 + 异步:4 - <NSThread: 0x600003328040>{number = 8, name = (null)}
2022-01-12 15:26:35.250277+0800 GCD之函数和队列[4716:166733] 并发 + 异步:2 - <NSThread: 0x600003305c40>{number = 3, name = (null)}
  1. 主队列 + 同步函数

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

造成死锁的原因分析如下:

  • 主队列有两个任务,顺序为: NSLog任务 - 同步block
  • 执行NSLog任务后,执行同步的block,会将 任务1(即 i=1) 加入到主队列,主队列顺序为 :NSLog任务 - 同步block - 任务1
  • 任务1 的执行需要 等待同步block执行完毕 才会执行,而 同步block 的执行需要 等待任务1执行完毕,所以就造成了 互相等待 的情况,即造成 死锁崩溃

【死锁现象】

  • 主线程 因为你 同步函数 的原因 等着先执行任务
  • 主队列等着主线程的任务 执行完毕再执行自己的任务
  • 主队列和主线程相互等待 会造成死锁
  1. 主队列 + 异步函数

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

1
2
3
4
5
6
7
8
9
10
// 主队列 + 异步函数
- (void)test6{
NSLog(@"主队列 + 异步:%@",[NSThread currentThread]);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (int i = 0; i < 5; i ++) {
dispatch_async(mainQueue, ^{
NSLog(@"主队列 + 异步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 16:27:53.197940+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:<_NSMainThread: 0x600001880000>{number = 1, name = main}
2022-01-12 16:27:53.213528+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:0 - <_NSMainThread: 0x600001880000>{number = 1, name = main}
2022-01-12 16:27:53.213718+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:1 - <_NSMainThread: 0x600001880000>{number = 1, name = main}
2022-01-12 16:27:53.213879+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:2 - <_NSMainThread: 0x600001880000>{number = 1, name = main}
2022-01-12 16:27:53.214016+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:3 - <_NSMainThread: 0x600001880000>{number = 1, name = main}
2022-01-12 16:27:53.214196+0800 GCD之函数和队列[5926:204672] 主队列 + 异步:4 - <_NSMainThread: 0x600001880000>{number = 1, name = main}
  1. 全局并发队列 + 同步函数

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

1
2
3
4
5
6
7
8
9
10
// 全局并发队列 + 同步函数
- (void)test7{
NSLog(@"全局并发 + 同步:%@",[NSThread currentThread]);
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 5; i ++) {
dispatch_sync(globalQueue, ^{
NSLog(@"全局并发 + 同步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 16:32:08.207844+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:<_NSMainThread: 0x60000031c140>{number = 1, name = main}
2022-01-12 16:32:08.207992+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:0 - <_NSMainThread: 0x60000031c140>{number = 1, name = main}
2022-01-12 16:32:08.208098+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:1 - <_NSMainThread: 0x60000031c140>{number = 1, name = main}
2022-01-12 16:32:08.208280+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:2 - <_NSMainThread: 0x60000031c140>{number = 1, name = main}
2022-01-12 16:32:08.208400+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:3 - <_NSMainThread: 0x60000031c140>{number = 1, name = main}
2022-01-12 16:32:08.208504+0800 GCD之函数和队列[6000:209221] 全局并发 + 同步:4 - <_NSMainThread: 0x60000031c140>{number = 1, name = main}
  1. 全局并发队列 + 异步函数

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

1
2
3
4
5
6
7
8
9
10
// 全局并发队列 + 异步函数
- (void)test8{
NSLog(@"全局并发 + 异步:%@",[NSThread currentThread]);
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 5; i ++) {
dispatch_async(globalQueue, ^{
NSLog(@"全局并发 + 异步:%d - %@",i,[NSThread currentThread]);
});
}
}

运行代码,查看打印结果如下

1
2
3
4
5
6
2022-01-12 16:34:18.147614+0800 GCD之函数和队列[6082:211916] 全局并发 + 异步:<_NSMainThread: 0x600002cd8340>{number = 1, name = main}
2022-01-12 16:34:18.147839+0800 GCD之函数和队列[6082:212058] 全局并发 + 异步:1 - <NSThread: 0x600002cdcdc0>{number = 4, name = (null)}
2022-01-12 16:34:18.147859+0800 GCD之函数和队列[6082:212057] 全局并发 + 异步:0 - <NSThread: 0x600002c97640>{number = 3, name = (null)}
2022-01-12 16:34:18.147880+0800 GCD之函数和队列[6082:212052] 全局并发 + 异步:2 - <NSThread: 0x600002cb9140>{number = 7, name = (null)}
2022-01-12 16:34:18.147880+0800 GCD之函数和队列[6082:212053] 全局并发 + 异步:3 - <NSThread: 0x600002c972c0>{number = 5, name = (null)}
2022-01-12 16:34:18.147892+0800 GCD之函数和队列[6082:212055] 全局并发 + 异步:4 - <NSThread: 0x600002c80000>{number = 8, name = (null)}

总结

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

相关面试题解析

  1. 【面试题-1】:并行队列 + 异步函数

下面代码的输出顺序是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)interview01{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1 %@",[NSThread currentThread]);
// 耗时
dispatch_async(queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
// 耗时
dispatch_async(queue, ^{
NSLog(@"3 %@",[NSThread currentThread]);
});
NSLog(@"4 %@",[NSThread currentThread]);
});
NSLog(@"5 %@",[NSThread currentThread]);
}

运行代码,查看打印结果如下

1
2
3
4
5
2022-01-12 16:50:01.867461+0800 GCD之函数和队列[6319:225209] 1 <_NSMainThread: 0x6000023f4840>{number = 1, name = main}
2022-01-12 16:50:01.867626+0800 GCD之函数和队列[6319:225209] 5 <_NSMainThread: 0x6000023f4840>{number = 1, name = main}
2022-01-12 16:50:01.867695+0800 GCD之函数和队列[6319:225342] 2 <NSThread: 0x600002399ec0>{number = 6, name = (null)}
2022-01-12 16:50:01.867840+0800 GCD之函数和队列[6319:225342] 4 <NSThread: 0x600002399ec0>{number = 6, name = (null)}
2022-01-12 16:50:01.867852+0800 GCD之函数和队列[6319:225345] 3 <NSThread: 0x6000023a8b00>{number = 4, name = (null)}
  • 异步函数不会阻塞 主队列,会开辟新线程 执行异步任务

代码分析:如下图所示,红线表示任务的执行顺序

  • 主线程 的任务队列为:任务1、异步block1、任务5,其中 异步block1 会比较耗费性能,任务1任务5 的任务复杂度是相同的,所以 任务1任务5 优先于 异步block1 执行
  • 异步block1 中,任务队列为:任务2、异步block2、任务4,其中 block2 相对比较耗费性能,任务2任务4 复杂度一样,所以 任务2任务4 优先于 block2 执行
  • 最后执行 block2 中的 任务3
  • 在极端情况下,可能会出现 任务2 优先 任务1任务5 执行,原因是出现了当前主线程卡顿或者延迟 的情况

代码修改

  • 【修改1】:将 并行队列 改成 串行队列,对结果没有任何影响,顺序仍然是 15243
  • 【修改2】:在 任务5 之前,休眠2s,即 sleep(2),执行的顺序为:12435,原因是因为 I/O 的打印,相比于休眠 2s,复杂度更简单,所以 异步block1 会优先于任务5执行。当然如果主队列阻塞,会出现其他的执行顺序。
  1. 【面试题-2】:并发队列 + 异步函数嵌套同步函数

下面代码的输出顺序是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)interview2{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1 %@",[NSThread currentThread]);
//异步函数
dispatch_async(queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
// 同步函数
dispatch_sync(queue, ^{
NSLog(@"3 %@",[NSThread currentThread]);
});
NSLog(@"4 %@",[NSThread currentThread]);
});
NSLog(@"5 %@",[NSThread currentThread]);
}

运行代码,查看打印结果如下

1
2
3
4
5
2022-01-12 17:37:11.034204+0800 GCD之函数和队列[7126:263322] 1 <_NSMainThread: 0x600000074300>{number = 1, name = main}
2022-01-12 17:37:11.034340+0800 GCD之函数和队列[7126:263322] 5 <_NSMainThread: 0x600000074300>{number = 1, name = main}
2022-01-12 17:37:11.034378+0800 GCD之函数和队列[7126:263438] 2 <NSThread: 0x600000039c40>{number = 3, name = (null)}
2022-01-12 17:37:11.034494+0800 GCD之函数和队列[7126:263438] 3 <NSThread: 0x600000039c40>{number = 3, name = (null)}
2022-01-12 17:37:11.034625+0800 GCD之函数和队列[7126:263438] 4 <NSThread: 0x600000039c40>{number = 3, name = (null)}

代码分析:

  • 任务1、block1、任务5 的分析和前面一致,执行顺序为:任务1 任务5 block1
  • 异步block1 中,任务2、block2、任务4 的执行顺序,由于 block2 是同步函数会阻塞当前线程,所以 任务4 需要等待 block2 中的 任务3 执行完成后,才能执行,所以异步block中的执行顺序是:任务2、任务3、任务4
  1. 【面试题-3】:串行队列 + 异步函数嵌套同步函数

下面代码的输出顺序是什么?会出现什么情况?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)interview3{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1 %@",[NSThread currentThread]);
//异步函数
dispatch_async(queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
// 同步函数
dispatch_sync(queue, ^{
NSLog(@"3 %@",[NSThread currentThread]);
});
NSLog(@"4 %@",[NSThread currentThread]);
});
NSLog(@"5 %@",[NSThread currentThread]);
}

运行代码,查看打印结果如下

分析:

如下图所示,红色代表任务执行顺序,黄色线表示等待

  • 首先,在主线程上有 任务1、异步block、任务5,首先执行 任务1,由于 异步block 复杂度高于 任务5,因此,执行顺序是 任务1 -> 任务5 -> 异步block
  • 在异步block中,开启了新的线程,执行顺序首先是 任务2,然后是 同步block同步函数会阻塞当前线程,所以执行 任务4 需要 等待任务3完成,由于 任务3的执行,需要 等待异步block完成异步block完成需要 等待任务4完成
  • 因此就会造成 任务4等待任务3,任务3等待任务4,即 互相等待 的局面,就会造成 死锁,这里有个重点是 关键的堆栈slow

修改

去掉任务4,执行顺序是什么?

  • 还是会死锁,因为 任务3等待的是异步block执行完毕,而异步block等待任务3完成

4、【面试题4-新浪】:异步函数 + 同步函数 + 并发队列

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)interview04{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.ZJ.Queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ // 耗时
NSLog(@"1 %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
});

// 同步
dispatch_sync(queue, ^{
NSLog(@"3 %@",[NSThread currentThread]);
});

NSLog(@"0 %@",[NSThread currentThread]);

dispatch_async(queue, ^{
NSLog(@"7 %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"8 %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"9 %@",[NSThread currentThread]);
});
}

打印结果是: (1、2、3无序) 0 (7、8、9无序),可以确定的 0 一定在 3 之后, 在789之前

分析:

  • 任务1任务2 是由于 异步函数 + 并发队列,会开启线程,所以没有固定顺序
  • 任务7、任务8、任务9同理,会开启线程,所以没有固定顺序
  • 任务3同步函数 + 并发队列,同步函数会 阻塞主线程,但是也只会阻塞0,所以,可以确定的是 0一定在3之后,在789之前

以下是不同的打印结果:

1
2
3
4
5
6
7
2022-01-14 14:45:42.843292+0800 GCD之函数和队列[18838:775240] 3 <_NSMainThread: 0x600001198a00>{number = 1, name = main}
2022-01-14 14:45:42.843329+0800 GCD之函数和队列[18838:775341] 1 <NSThread: 0x600001189cc0>{number = 5, name = (null)}
2022-01-14 14:45:42.843361+0800 GCD之函数和队列[18838:775340] 2 <NSThread: 0x600001195a40>{number = 7, name = (null)}
2022-01-14 14:45:42.843415+0800 GCD之函数和队列[18838:775240] 0 <_NSMainThread: 0x600001198a00>{number = 1, name = main}
2022-01-14 14:45:42.843546+0800 GCD之函数和队列[18838:775340] 7 <NSThread: 0x600001195a40>{number = 7, name = (null)}
2022-01-14 14:45:42.843628+0800 GCD之函数和队列[18838:775341] 8 <NSThread: 0x600001189cc0>{number = 5, name = (null)}
2022-01-14 14:45:42.843653+0800 GCD之函数和队列[18838:775343] 9 <NSThread: 0x60000119ce80>{number = 6, name = (null)}
1
2
3
4
5
6
7
8
2022-01-14 14:50:18.831754+0800 GCD之函数和队列[18899:780910] 3 <_NSMainThread: 0x600000054a00>{number = 1, name = main}
2022-01-14 14:50:18.831835+0800 GCD之函数和队列[18899:781015] 2 <NSThread: 0x600000010e00>{number = 6, name = (null)}
2022-01-14 14:50:18.831801+0800 GCD之函数和队列[18899:781016] 1 <NSThread: 0x600000052340>{number = 5, name = (null)}
2022-01-14 14:50:18.831887+0800 GCD之函数和队列[18899:780910] 0 <_NSMainThread: 0x600000054a00>{number = 1, name = main}
2022-01-14 14:50:18.832053+0800 GCD之函数和队列[18899:781016] 7 <NSThread: 0x600000052340>{number = 5, name = (null)}
2022-01-14 14:50:18.832058+0800 GCD之函数和队列[18899:781015] 8 <NSThread: 0x600000010e00>{number = 6, name = (null)}
2022-01-14 14:50:18.832082+0800 GCD之函数和队列[18899:781019] 9 <NSThread: 0x600000011b80>{number = 3, name = (null)}

1
2
3
4
5
6
7
2022-01-14 14:51:47.433576+0800 GCD之函数和队列[18934:784384] 3 <_NSMainThread: 0x6000030280c0>{number = 1, name = main}
2022-01-14 14:51:47.433577+0800 GCD之函数和队列[18934:784516] 2 <NSThread: 0x6000030741c0>{number = 4, name = (null)}
2022-01-14 14:51:47.433578+0800 GCD之函数和队列[18934:784518] 1 <NSThread: 0x60000304d540>{number = 6, name = (null)}
2022-01-14 14:51:47.433718+0800 GCD之函数和队列[18934:784384] 0 <_NSMainThread: 0x6000030280c0>{number = 1, name = main}
2022-01-14 14:51:47.433880+0800 GCD之函数和队列[18934:784518] 7 <NSThread: 0x60000304d540>{number = 6, name = (null)}
2022-01-14 14:51:47.433890+0800 GCD之函数和队列[18934:784513] 9 <NSThread: 0x600003040000>{number = 5, name = (null)}
2022-01-14 14:51:47.433891+0800 GCD之函数和队列[18934:784516] 8 <NSThread: 0x6000030741c0>{number = 4, name = (null)}

5、【面试题-5-美团】:下面代码中,队列的类型有几种?

1
2
3
4
5
6
7
8
9
10
11
// 串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.ZJ.Queue", NULL);

// 并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.ZJ.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);

队列总共有两种:串行队列并发队列

  • 串行队列:serialQueue、mainQueue
  • 并发队列:concurrentQueue、globalQueue
  • Post title:OC底层原理26:GCD之函数与队列
  • Post author:张建
  • Create time:2021-03-10 14:45:48
  • Post link:https://redefine.ohevan.com/2021/03/10/OC底层原理/OC底层原理26:GCD之函数与队列/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.