关键词英文简译:
dispatch: 派遣 / 派出 / 发出 / 发送
sync: 同步, 同时
async: 异步, 不同时
queue: 队列
问题1: GCD调用的四种组合/ 讲一下你对GCD的了解
-
同步串行
:同步
分配任务到串行队列
dispatch_sync(serial_queue, ^{ 任务 })
-
异步串行
:异步
分配任务到串行队列
dispatch_async(serial_queue, ^{ 任务 })
-
同步并行
:同步
分配任务到并发队列
dispatch_sync(concurrent_queue, ^{ 任务 })
-
异步并行
:异步
分配任务到并发队列
dispatch_async(concurrent_queue, ^{ 任务 })
同步函数不具备开启线程的能力
,无论是什么队列都不会开启线程;异步函数具备开启线程的能力
,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)
同步函数 dispatch_sync
- 串行队列:
不会开启线程
- 并发队列:
不会开启线程
异步函数 dispatch_async
- 串行队列:
1条线程
- 并发队列:
N条线程
留意下, 异步有不新开线程情况
任务追加线程已存在
,比如 main 线程,则不会开线程,而是使用线程。例如: 在串行队列Queue
的任务A
里异步将任务B
追加到队列Queue
中,此时任务B
和任务A
在一个线程,不开线程。
问题2: GCD自定义queue有几种类型
serial
串行
又称为private dispatch queues,同一时刻只执行一个
任务,并按添加到serial的顺序执行。当创建多个serial queue
时,虽然它们各自是同步执行的,但serial queue
与serial queue
之间是并发执行的。serial queue
通常用于同步访问特定的资源或数据。
concurrent
并行
又称为global dispatch queue,同一时刻可执行多个
任务,任务开始执行的顺序按添加的顺序执行,但是执行完成的顺序是随机的,同时可以创建执行的任务数量依赖系统条件。
Main dispatch queue
主队列
全局可用的serial queue,它是在应用程序主线程上执行任务的。
看下官方注释:
主队列的并不完全像常规串行队列,
当在非UI应用程序的进程中使用时,它可能会产生不必要的副作用
(守护程序)对于此类进程,应避免使用主队列。
调用main() 之前的主线程会自动创建主队列
所以主队列其实是一种特殊的串行队列, 题目只回答 串行&并行 也是对的
dispatch_get_global_queue
全局队列, 是并行队列
问题3: 下面代码运行结果会什么样?
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
考察同步串行问题
答案:
先打印 1 然后报错 Crash
其实因为这里的dispatch_sync
是个死锁
// 死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
-
死锁原因
: 由队列引起的循环等待 (留意下并不是由线程引起的循环等待)
这段代码的进行的逻辑为
我们首先在主队列里面提交了一个
viewDidLoad
任务, 接下来又提交了一个Block
任务。当然这两个任务都被分配到主线程中去执行。现在我们分配
viewDidLoad
到主线程中去处理, 因为viewDidLoad
中有dispatch_get_main_queue 的 Block
, 那么只有Block调用之后viewDidLoad
才能往下走。而主队列有个特点:
先进先出 FIFO
, 即block
执行完成必须要等待viewDidLoad
结束(完成)才可以。这样就形成了一个相互等待的场面 --- 死锁
验证:
问题4: 下面代码运行结果会什么样?
NSLog(@"1");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"2");
});
NSLog(@"3");
跟问题3不同, 这端代码涉及2个队列, 主队列和自定义串行队列queue
- 主队列提交一个
viewdidload
方法, 当然这个方法在主线程
里面执行 -
viewdidload
执行中, 需要同步提交一个任务到自定义串行队列
上面, 同步提交需要在相同线程执行, 固也在主线程
执行 - 串行队列在
主线程
执行任务之后, 再去继续执行主队列后续代码逻辑
所以正常运行
答案:
正常运行 1 2 3
当然如果创建的队列是主队列, 如下
// dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_get_main_queue();
那么问题4例子还是跟问题3一样, 死锁 crash
问题5: 下面代码运行结果会什么样?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- 同步提交任务无论串行或者并发都是在当前线程中执行。上面例子在
viewDidLoad
里面执行, 即主线程里面执行。 - 先执行
NSLog(@"1")
然后走同步并发队列dispatch_sync(queue, ^{
, 里面的NSLog(@"2")
- 里层的
dispatch_sync(queue, ^{
,也是个串行并发队列, 在当前线程执行即主线程 - 同时由于并发队列特点, 我们提交到这个队列里面的block可以并发执行, 那么继续执行
NSLog(@"3")
- 之后继续执行
NSLog(@"4")
,NSLog(@"5")
答案:
正常运行 1 2 3 4 5
问题6: 下面代码运行结果会什么样?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("test1", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
其实这个道题跟问题3 道理相同
原因都是: 由队列引起的循环等待造成 死锁
同步提交任务无论串行或者并发都是在当前线程中执行。上面例子在
viewDidLoad
里面执行, 即主线程里面执行。里层外层的串行队列都是相同线程, 即主线程中执行。由于是同步方式外层dispatch_sync结束需要等待里层dispatch_sync结束, 而里层dispatch_sync结束有需要等待外层dispatch_sync结束, 这样形成循环等待导致死锁
答案:
异常运行 1 2 Crash
问题7: 下面代码运行结果会什么样?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
}
异步分配到串行队列, 这里是主队列。viewDidLoad
执行结束之后, 执行异步任务dispatch_async
答案:
运行正常 1 2
问题8: 下面代码运行结果会什么样?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(testAction1)];
[self performSelector:@selector(testAction2) withObject:nil afterDelay:0];
NSLog(@"4");
});
}
- (void)testAction1 {
NSLog(@"2");
}
- (void)testAction2 {
NSLog(@"3");
}
异步到一个全局队列, 先执行
NSLog(@"1");
执行
performSelector :
方法, 由于performSelector
是NSObject上的对象方法, 正常执行
- 但
performSelector: withObject: afterDelay:
是runloop
方法, 要提交到runloop
上执行, 而GCD底层不会创建runloop
的, 所以失效不会执行, 往下走
- 1打印完打印2, 不会执行
performSelector: withObject: afterDelay:
, 接下来打印4, 所以结果1, 2, 4
答案:
运行正常 1 2 4
问题9: 怎样用GCD实现多读单写
例如内存中维护一个字典, 有多个读者和写者操作这块数据
- 读者读者
并发
, 可以同一时间可以多个读者读取数据, 相互不受影响 - 读者写者
互斥
, 有读者在读取数据, 就不能有写的线程在处理 - 写者写者
互斥
, 有写者在写数据, 其他就不能写数据, 防止数据错乱
所以我们设计的模式就是
当做写处理
的时候通过一个"栅栏"来挡住读处理
, 等写处理
完成之后再进行读处理
GCD 栅栏函数
dispatch_barrier_async(异步队列, ^{ // 写操作 } );
// barrier: 障碍, 屏障, 隔阂, 关卡, 阻力, 分界线
看个例子, 有三组并发队列, 队列A读取, 队列B更改数据, 队列C读取更改完的数据
如果我们不做任何操作, 如下
@interface ViewController ()
@property(nonatomic, assign) int a;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"队列A读取: %d", self.a);
});
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
weakSelf.a = weakSelf.a + 1;
NSLog(@"队列B数据变更: %d", self.a);
}
});
dispatch_async(queue, ^{
NSLog(@"队列C读取: %d", self.a);
});
}
错误打印结果
很明显并未达到预期, 那么我们加一个栅栏, 改成
dispatch_async(queue, ^{
NSLog(@"队列A读取: %d", self.a);
});
__weak typeof(self) weakSelf = self;
// dispatch_async(queue, ^{
//
// for (int i = 0; i < 10; i++) {
// weakSelf.a = weakSelf.a + 1;
//
// NSLog(@"队列B数据变更: %d", self.a);
// }
// });
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 10; i++) {
weakSelf.a = weakSelf.a + 1;
NSLog(@"栅栏: %d", self.a);
}
});
dispatch_async(queue, ^{
NSLog(@"队列C读取: %d", self.a);
});
可看出这样满足我们的条件
问题10: viewDidLoad中有3组异步并发队列, 每个队列顺序打印10个数, 实现1~30打印
其实这道题也是考察栅栏方法dispatch_barrier_async
, 建议先看问题9
如果我们正常写3个并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//打印1到10
for (int i = 1; i < 11; i++) {
NSLog(@"0到10: %d",i);
}
});
dispatch_async(queue, ^{
//打印11到20
for (int i = 11; i < 21; i++) {
NSLog(@"11到20: %d",i);
}
});
dispatch_async(queue, ^{
//打印21到30
for (int i = 21; i < 31; i++) {
NSLog(@"21到30: %d",i);
}
});
打印结果
那么这个时候dispatch_barrier_async
就可以起到这个作用
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//打印1到10
for (int i = 1; i < 11; i++) {
NSLog(@"0到10: %d",i);
}
});
// dispatch_async(queue, ^{
// //打印11到20
// for (int i = 11; i < 21; i++) {
// NSLog(@"11到20: %d",i);
// }
// });
dispatch_barrier_async(queue, ^{
//打印11到20
for (int i = 11; i < 21; i++) {
NSLog(@"11到20: %d",i);
}
});
dispatch_async(queue, ^{
//打印21到30
for (int i = 21; i < 31; i++) {
NSLog(@"21到30: %d",i);
}
});
问题11: 使用GCD实现: A, B, C 三个任务并发, 完成后执行任务D
其实这道题就是考察 dispatch_group_async
的使用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *arr = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 3; i++) {
dispatch_group_async(group, queue, ^{
NSLog(@"执行: %d 当前数组: %@", i, arr);
[arr addObject:@(i)];
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部执行完: %@", arr);
});
}
问题11: 什么是队列
队列: 是一种操作受限制的线性表, 是一种先进先出( FIFO )的数据结构
更加详细参考:
问题12: 队列和线程关系
-
队列
负责管理任务
, 按照FIFO(先进先出)规则将任务交给线程
去执行 -
线程
是由线程池和系统进行管理, 跟队列
无关, 也无任务
概念
其实 线程
和队列
是两个层级概念. 线程
是程序执行的单元, 或者说是系统级的运算调度单位. 队列
是线性表, 一种数据结构.
问题13:下面代码运行结果会什么样?
答案:
运行正常 1 5 2 4 3
任务执行, 跟 线程状态, CPU调度, 线程池调度, 队列优先级, 任务复杂度等有关
回到上面题目, 先留意下queue 是 DISPATCH_QUEUE_CONCURRENT 并发队列, 由上到下开始执行, 先打印1.
异步dispatch_async
不会堵塞主线程但是会耗时, 系统会先执行后面同步 打印5.
接着执行异步 打印2.
同理里面又含一个异步async, 系统跳过先执行后面 打印4.
接着执行里面异步async 打印3, 结果 1 5 2 4 3
打印结果:
如果把里面的dispatch_async
换成 dispatch_sync
结果就会变成 1 5 2 4 3
问题14:下面代码运行结果会什么样?
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
});
NSLog(@"4");
});
dispatch_sync(queue, ^{
NSLog(@"5");
});
答案:
运行正常 1 2 3 4 5
留意下queue 是 DISPATCH_QUEUE_SERIAL 串行队列
打印结果:
问题14: vb 面试题, 下面代码运行结果选什么?
dispatch_queue_t queue = dispatch_queue_create("com.vb.inter", 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");
});
A: 1230789
B: 1237890
C: 3120798
D: 2137890
1, 2 是dispatch_async
异步, 打印位置不一定, 7, 8, 9 同理, 但是3 是dispatch_sync
同步以及 0 是直接打印, 会在7, 8, 9 异步前面, 排除B, D. AC都有可能发生, 固 答案: AC