面试题答案
一键面试避免死锁的方法
- 避免循环依赖:确保不同任务之间不会形成互相等待资源的循环。例如,若任务A等待任务B释放资源,而任务B又等待任务A释放资源,就会产生死锁。应梳理任务间的依赖关系,打破这种循环。
- 合理设置队列优先级:避免高优先级任务等待低优先级任务释放资源,导致低优先级任务永远无法执行。例如,使用
dispatch_set_target_queue
函数来设置队列优先级关系。 - 避免在同步队列中同步执行任务:不要在主队列(串行队列)中同步执行任务,因为这会造成主线程等待任务完成,而任务又在等待主线程继续执行,从而引发死锁。如不要在
dispatch_sync(dispatch_get_main_queue(), ^{ /* 任务 */ });
这种场景下在主队列中同步执行任务。
排查和解决死锁问题的思路及步骤
- 定位死锁位置
- 使用工具:利用 Instruments 中的 Deadlocks 工具,它可以捕获死锁并指出发生死锁的线程和相关代码位置。运行应用程序,当死锁发生时,Instruments 会暂停并显示死锁信息。
- 日志分析:在代码中适当位置添加日志,例如在进入和离开临界区(使用锁的区域)时记录日志。通过分析日志,查看哪些线程在等待资源,以及等待的顺序,从而找出可能形成死锁的地方。
- 分析死锁原因
- 资源依赖分析:检查死锁涉及的线程所访问的资源,看是否存在循环依赖。例如,线程1持有资源A并等待资源B,而线程2持有资源B并等待资源A,这就是典型的循环依赖导致的死锁。
- 同步机制检查:查看代码中使用的同步机制(如锁、信号量等)是否使用不当。比如,锁的嵌套使用是否合理,是否在获取锁后没有及时释放。
- 解决死锁问题
- 打破循环依赖:重新设计资源获取顺序,确保所有线程按照相同的顺序获取资源,避免循环等待。例如,总是先获取资源A,再获取资源B,无论哪个线程都遵循此顺序。
- 优化同步机制:检查锁的使用,确保锁的获取和释放操作正确配对。可以考虑使用更细粒度的锁,减少锁的持有时间,降低死锁发生的概率。例如,将对一个大对象的锁分解为对几个小部分的锁。
GCD多线程代码性能优化(高并发场景下)
- 队列优化
- 使用并发队列:根据任务类型选择合适的队列,对于可以并行执行的任务,使用并发队列(
dispatch_get_global_queue
获取的全局并发队列或dispatch_queue_create
创建的自定义并发队列),充分利用多核处理器的性能。例如,处理大量数据的计算任务可以放在并发队列中执行。 - 控制并发度:通过设置并发队列的质量服务(QoS)类来控制并发度。例如,对于I/O密集型任务,可以设置为
NSQualityOfServiceBackground
,系统会根据资源情况合理分配并发度,避免过多线程竞争资源导致性能下降。
- 使用并发队列:根据任务类型选择合适的队列,对于可以并行执行的任务,使用并发队列(
- 任务优化
- 减少任务粒度:将大任务拆分成多个小任务,使系统能更灵活地调度这些任务,提高并发执行效率。例如,对于一个处理大量数据的任务,可以拆分成多个处理小块数据的子任务,并行执行。
- 避免不必要的同步:尽量减少在并发任务中使用同步操作(如
dispatch_sync
),因为同步操作会阻塞线程,降低并发性能。只有在必要时,如需要保证数据一致性时才使用同步操作。
- 数据共享优化
- 使用线程安全的数据结构:当多个线程需要访问共享数据时,使用线程安全的数据结构,如
NSMutableArray
的线程安全替代品NSMutableArray
(在GCD中使用dispatch_semaphore_t
等同步机制保护),或使用NSCache
(它本身是线程安全的),避免数据竞争导致的性能问题。 - 减少数据共享:尽量避免多个线程频繁访问共享数据,如果可以,将数据复制到每个线程的本地空间进行处理,减少同步开销。例如,在图像处理中,可以将图像数据复制到每个线程的本地缓冲区进行处理,处理完成后再合并结果。
- 使用线程安全的数据结构:当多个线程需要访问共享数据时,使用线程安全的数据结构,如