GCD 经典代码题

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并不是"立即执行"的,它是被提交到队列,然后当前线程等待它完成。但是:
  1. 任务A已经占据了当前线程,并且正在等待任务B完成
  2. 任务B被提交到同一个并发队列,它需要等待资源(线程)来执行
  3. 当前线程被任务A阻塞,无法执行任务B
  4. 其他线程(如果有)也无法"抢占"当前线程正在执行的任务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, 425 顺序可能不确定)

分析:

  • 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, 412 顺序不确定,但 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 特性导致相互等待
不同串行队列相互同步 ⚠️ 可能死锁,看执行顺序
并发队列同步嵌套 并发队列允许同步执行新任务
全局队列同步到主队列 不同线程不会死锁

面试回答技巧

回答这类问题时,建议采用以下结构:

  1. 明确队列类型:"这是一个串行/并发队列"
  2. 分析执行线程:"当前代码在主线程/后台线程"
  3. 判断阻塞关系:"dispatch_sync 会阻塞当前线程"
  4. 考虑队列特性:"串行队列一次只能执行一个任务"
  5. 得出结论:"所以会死锁/顺序执行"

示例回答(针对题目1):

"这段代码会死锁。因为当前是在主线程执行,dispatch_sync 会阻塞主线程等待 block 执行。但 block 被提交到主队列,主队列的任务必须在主线程执行,而主线程正被阻塞。这就形成了主线程等待自己执行任务的循环等待,导致死锁。"

这些题目考察对 GCD 核心概念的理解:队列类型、同步/异步、线程模型、死锁条件。掌握这些能很好体现你对多线程编程的深入理解。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容