iOS GCD 经典面试题 - 打印顺序分析
死锁时,程序会出现以下现象:
卡住不动,界面无响应,无法操作。
控制台没有后续日志输出。
相关线程(如主线程)一直处于等待状态,无法继续执行后续代码。
如果是主线程死锁,App 会假死,系统可能会提示“应用无响应”或强制关闭。
dispatch_sync:当前线程执行,不开启新线程,阻塞当前线程。dispatch_async:可能开启新线程,后台线程执行,不阻塞当前线程。并发队列,可以同时执行多个任务。
题目1: 主队列 + 同步 = 死锁
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
答案: 只会打印 1,然后死锁
分析:
- 当前代码在主线程执行
-
dispatch_sync会阻塞当前线程等待 block 执行完成 - 但主队列的任务必须在主线程执行,而主线程正在执行当前代码并等待
- 相互等待 → 死锁
解决:
// 用异步不会死锁
NSLog(@"1");
// 异步任务会在主线程空闲时执行。
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");`
// 这样会依次输出 1、3,然后 2。
// 因为后台线程可以等待主线程执行完 block。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"A");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"B");
});
NSLog(@"C");
});
// 异步地将一个 block 投递到全局并发队列(后台线程)执行。
// block 内的代码不会阻塞当前线程,会在后台线程执行
// A是后台线程, B是主线城, C是后台线程执行
题目2: 串行队列的嵌套同步
// 在串行队列中嵌套同步调用会导致死锁
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
// serialQueue 是个串行队列 dispatch_async 把一个block添加到队列里, dispatch_sync把一个block加到串行队列里, dispatch_async 的block在等dispatch_sync 的block执行完才能往下执行。 但是dispatch_sync 是在串行队列里 , 当前已经有一个任务执行了, 所以dispatch_sync 要等上一个任务执行, 所以互相等待死锁了
答案: 1, 5, 2,然后死锁
分析:
-
1在主线程立即执行 -
dispatch_async不阻塞,继续执行5 - 串行队列顺序执行任务:先执行第一个 block(打印
2) - 在第一个 block 内部,
dispatch_sync向同一个串行队列提交第二个任务 - 串行队列:同一时刻只允许一个任务执行。第一个任务等待第二个完成,第二个任务排队等待第一个完成 → 死锁
- dispatch_async:异步提交任务,不阻塞当前线程。
- dispatch_sync:同步提交任务,当前线程会等待 block 执行完毕。
题目3: 并发队列的同步嵌套
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^{
NSLog(@"2");
dispatch_sync(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
答案: `1, 2 死锁
分析:
-
dispatch_sync会阻塞当前线程直到提交的 block 执行完成 - 它会在当前线程执行 block(不一定会创建新线程)
- 但关键是:它会等待 block 执行完毕才返回
- 这里任务B并不是"立即执行"的,它是被提交到队列,然后当前线程等待它完成。但是:
- 任务A已经占据了当前线程,并且正在等待任务B完成
- 任务B被提交到同一个并发队列,它需要等待资源(线程)来执行
- 当前线程被任务A阻塞,无法执行任务B
- 其他线程(如果有)也无法"抢占"当前线程正在执行的任务A
题目4: 全局队列与主队列交互
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
答案: 1, 5, 2, 3, 4(2 和 5 顺序可能不确定)
分析:
-
1在主线程执行 - 异步提交到全局队列,不阻塞主线程,继续执行
5 - 全局队列的任务在后台线程执行
2 - 同步回到主队列执行
3(这里不会死锁,因为当前是后台线程) - 继续执行
4
题目5: 多个队列的复杂交互
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue1, ^{
NSLog(@"2");
dispatch_sync(queue2, ^{
NSLog(@"3");
});
NSLog(@"4");
});
dispatch_async(queue2, ^{
NSLog(@"5");
dispatch_sync(queue1, ^{
NSLog(@"6");
});
NSLog(@"7");
});
NSLog(@"8");
答案: 1, 8 最先打印,2, 5 随后顺序不定,然后可能死锁
分析:
-
1, 8在主线程立即执行 - 两个异步任务分别在 queue1 和 queue2 执行
- 如果执行顺序导致 queue1 和 queue2 相互等待对方的同步任务 → 死锁
- 例如:queue1 等待 queue2 的
3完成,queue2 等待 queue1 的6完成
题目6: dispatch_barrier_async
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{ sleep(1); NSLog(@"1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"2"); });
// 栅栏任务会等前面的所有任务执行完毕后再执行,并且在它执行期间,队列不会并发执行其他任务。
dispatch_barrier_async(concurrentQueue, ^{ NSLog(@"barrier"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"3"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"4"); });
答案: 2, 1, barrier, 3, 4(1 和 2 顺序不确定,但 barrier 一定在它们之后)
分析:
- barrier 会等待之前所有并发任务完成
- barrier 自身执行时,队列相当于串行
- barrier 完成后,后续任务继续并发执行
题目7: dispatch_group 等待
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"任务1完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"任务2完成");
dispatch_group_leave(group);
});
NSLog(@"等待中...");
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"所有任务完成");
答案: 等待中..., 任务2完成, 任务1完成, 所有任务完成
分析:
-
dispatch_group_wait阻塞当前线程直到所有任务完成 - 任务按实际完成时间打印
- 所有任务完成后才执行最后打印
核心考点总结
| 场景 | 是否死锁 | 关键原因 |
|---|---|---|
| 主队列同步(主线程) | ✅ | 主线程等待自己 |
| 同一串行队列嵌套同步 | ✅ | 队列 FIFO 特性导致相互等待 |
| 不同串行队列相互同步 | ⚠️ | 可能死锁,看执行顺序 |
| 并发队列同步嵌套 | ❌ | 并发队列允许同步执行新任务 |
| 全局队列同步到主队列 | ❌ | 不同线程不会死锁 |
面试回答技巧
回答这类问题时,建议采用以下结构:
- 明确队列类型:"这是一个串行/并发队列"
- 分析执行线程:"当前代码在主线程/后台线程"
- 判断阻塞关系:"dispatch_sync 会阻塞当前线程"
- 考虑队列特性:"串行队列一次只能执行一个任务"
- 得出结论:"所以会死锁/顺序执行"
示例回答(针对题目1):
"这段代码会死锁。因为当前是在主线程执行,
dispatch_sync会阻塞主线程等待 block 执行。但 block 被提交到主队列,主队列的任务必须在主线程执行,而主线程正被阻塞。这就形成了主线程等待自己执行任务的循环等待,导致死锁。"
这些题目考察对 GCD 核心概念的理解:队列类型、同步/异步、线程模型、死锁条件。掌握这些能很好体现你对多线程编程的深入理解。