MST

星途 面试题库

面试题:Objective-C多线程调试中常见的死锁场景及解决方法

在Objective-C多线程编程中,经常会遇到死锁问题。请描述至少两种常见的死锁场景,并说明如何使用调试工具(如LLDB)来诊断和解决这些死锁问题。
23.1万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

常见死锁场景

  1. 嵌套锁场景
    • 描述:线程A获取锁1,然后尝试获取锁2;同时线程B获取锁2,然后尝试获取锁1。例如,假设有两个资源resource1resource2,分别由lock1lock2保护。线程A先锁定lock1,然后试图锁定lock2;线程B先锁定lock2,然后试图锁定lock1,这样就形成了死锁。
    • 示例代码
NSLock *lock1 = [[NSLock alloc] init];
NSLock *lock2 = [[NSLock alloc] init];

// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock1 lock];
    NSLog(@"线程A获取了lock1");
    [lock2 lock];
    NSLog(@"线程A获取了lock2");
    [lock2 unlock];
    [lock1 unlock];
});

// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock2 lock];
    NSLog(@"线程B获取了lock2");
    [lock1 lock];
    NSLog(@"线程B获取了lock1");
    [lock1 unlock];
    [lock2 unlock];
});
  1. 递归锁错误使用场景
    • 描述:虽然递归锁允许同一线程多次获取锁而不造成死锁,但如果在使用非递归锁时,一个线程尝试多次获取该锁,就会导致死锁。例如,一个方法methodA获取了锁,然后在methodA内部调用另一个方法methodBmethodB又尝试获取同一个锁。
    • 示例代码
NSLock *nonRecursiveLock = [[NSLock alloc] init];

- (void)methodA {
    [nonRecursiveLock lock];
    NSLog(@"methodA获取锁");
    [self methodB];
    [nonRecursiveLock unlock];
}

- (void)methodB {
    [nonRecursiveLock lock];
    NSLog(@"methodB获取锁");
    [nonRecursiveLock unlock];
}
  1. 循环依赖场景
    • 描述:多个对象之间存在循环依赖关系,并且每个对象在访问其他对象的资源时需要获取锁。例如,对象A持有对象B的引用,对象B持有对象C的引用,对象C又持有对象A的引用。当每个对象都试图获取下一个对象的资源并锁定时,就可能形成死锁。
    • 示例代码
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end

@interface ClassB : NSObject
@property (nonatomic, strong) ClassC *classC;
@end

@interface ClassC : NSObject
@property (nonatomic, strong) ClassA *classA;
@end

@implementation ClassA
- (void)accessResources {
    [self.classB lockAndAccessResources];
}
@end

@implementation ClassB
- (void)lockAndAccessResources {
    // 假设这里有锁机制
    [self.classC lockAndAccessResources];
}
@end

@implementation ClassC
- (void)lockAndAccessResources {
    // 假设这里有锁机制
    [self.classA accessResources];
}
@end

使用LLDB诊断和解决死锁问题

  1. 诊断死锁
    • 暂停程序:当怀疑发生死锁时,在Xcode中点击暂停按钮,暂停程序的执行。
    • 查看线程状态:在LLDB控制台中,使用thread list命令查看所有线程的状态。死锁时,通常会看到多个线程处于等待状态。例如:
(lldb) thread list
* thread #1, queue = 'com.apple.main - queue', stop reason = signal SIGSTOP
  thread #2, queue = 'com.apple.root.default - qos', stop reason = signal SIGSTOP
  thread #3, queue = 'com.apple.root.default - qos', stop reason = signal SIGSTOP
  • 查看线程堆栈:使用thread backtrace命令查看每个线程的堆栈信息,以确定线程在等待什么资源。例如,要查看线程2的堆栈,执行thread backtrace 2。堆栈信息可能会显示线程正在等待获取某个锁,从而帮助定位死锁的源头。
  1. 解决死锁
    • 修复锁获取顺序:根据诊断结果,如果是嵌套锁死锁,调整锁的获取顺序,确保所有线程以相同的顺序获取锁。例如,在前面嵌套锁的例子中,让线程A和线程B都先获取lock1,再获取lock2
    • 正确使用递归锁:如果是递归锁错误使用场景,将非递归锁替换为递归锁(如NSRecursiveLock),或者调整代码逻辑避免重复获取非递归锁。
    • 打破循环依赖:在循环依赖场景中,分析对象之间的依赖关系,打破循环。可以通过重新设计对象的关系,或者引入中间层来管理资源,避免直接的循环依赖。