MST
星途 面试题库

面试题:Objective-C中GCD多线程编程之死锁与优化

在Objective-C使用GCD编程时,如何避免死锁情况的发生?假设你在一个多线程项目中遇到了死锁,描述你排查和解决死锁问题的思路及步骤。另外,如何对GCD多线程代码进行性能优化,尤其是在高并发场景下?
28.8万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

避免死锁的方法

  1. 避免循环依赖:确保不同任务之间不会形成互相等待资源的循环。例如,若任务A等待任务B释放资源,而任务B又等待任务A释放资源,就会产生死锁。应梳理任务间的依赖关系,打破这种循环。
  2. 合理设置队列优先级:避免高优先级任务等待低优先级任务释放资源,导致低优先级任务永远无法执行。例如,使用dispatch_set_target_queue函数来设置队列优先级关系。
  3. 避免在同步队列中同步执行任务:不要在主队列(串行队列)中同步执行任务,因为这会造成主线程等待任务完成,而任务又在等待主线程继续执行,从而引发死锁。如不要在dispatch_sync(dispatch_get_main_queue(), ^{ /* 任务 */ });这种场景下在主队列中同步执行任务。

排查和解决死锁问题的思路及步骤

  1. 定位死锁位置
    • 使用工具:利用 Instruments 中的 Deadlocks 工具,它可以捕获死锁并指出发生死锁的线程和相关代码位置。运行应用程序,当死锁发生时,Instruments 会暂停并显示死锁信息。
    • 日志分析:在代码中适当位置添加日志,例如在进入和离开临界区(使用锁的区域)时记录日志。通过分析日志,查看哪些线程在等待资源,以及等待的顺序,从而找出可能形成死锁的地方。
  2. 分析死锁原因
    • 资源依赖分析:检查死锁涉及的线程所访问的资源,看是否存在循环依赖。例如,线程1持有资源A并等待资源B,而线程2持有资源B并等待资源A,这就是典型的循环依赖导致的死锁。
    • 同步机制检查:查看代码中使用的同步机制(如锁、信号量等)是否使用不当。比如,锁的嵌套使用是否合理,是否在获取锁后没有及时释放。
  3. 解决死锁问题
    • 打破循环依赖:重新设计资源获取顺序,确保所有线程按照相同的顺序获取资源,避免循环等待。例如,总是先获取资源A,再获取资源B,无论哪个线程都遵循此顺序。
    • 优化同步机制:检查锁的使用,确保锁的获取和释放操作正确配对。可以考虑使用更细粒度的锁,减少锁的持有时间,降低死锁发生的概率。例如,将对一个大对象的锁分解为对几个小部分的锁。

GCD多线程代码性能优化(高并发场景下)

  1. 队列优化
    • 使用并发队列:根据任务类型选择合适的队列,对于可以并行执行的任务,使用并发队列(dispatch_get_global_queue获取的全局并发队列或dispatch_queue_create创建的自定义并发队列),充分利用多核处理器的性能。例如,处理大量数据的计算任务可以放在并发队列中执行。
    • 控制并发度:通过设置并发队列的质量服务(QoS)类来控制并发度。例如,对于I/O密集型任务,可以设置为NSQualityOfServiceBackground,系统会根据资源情况合理分配并发度,避免过多线程竞争资源导致性能下降。
  2. 任务优化
    • 减少任务粒度:将大任务拆分成多个小任务,使系统能更灵活地调度这些任务,提高并发执行效率。例如,对于一个处理大量数据的任务,可以拆分成多个处理小块数据的子任务,并行执行。
    • 避免不必要的同步:尽量减少在并发任务中使用同步操作(如dispatch_sync),因为同步操作会阻塞线程,降低并发性能。只有在必要时,如需要保证数据一致性时才使用同步操作。
  3. 数据共享优化
    • 使用线程安全的数据结构:当多个线程需要访问共享数据时,使用线程安全的数据结构,如NSMutableArray的线程安全替代品NSMutableArray(在GCD中使用dispatch_semaphore_t等同步机制保护),或使用NSCache(它本身是线程安全的),避免数据竞争导致的性能问题。
    • 减少数据共享:尽量避免多个线程频繁访问共享数据,如果可以,将数据复制到每个线程的本地空间进行处理,减少同步开销。例如,在图像处理中,可以将图像数据复制到每个线程的本地缓冲区进行处理,处理完成后再合并结果。