MST

星途 面试题库

面试题:Objective-C多线程编程中锁的选择与死锁预防

在Objective-C多线程编程时,可能会使用到各种锁来保证数据的一致性和线程安全,如NSLock、@synchronized、NSCondition等。请分析不同类型锁的优缺点,以及在复杂多线程场景下如何避免死锁的发生,假设存在多个线程访问多个资源的情况,给出具体的预防策略和代码示例。
23.5万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

1. 不同类型锁的优缺点分析

NSLock

  • 优点
    • 简单易用,在需要锁定的代码前后调用 lockunlock 方法即可。
    • 性能较好,适用于简单的同步场景。
  • 缺点
    • 不支持递归调用,如果一个线程对同一个锁多次调用 lock 而没有相应次数的 unlock,会导致死锁。
    • 没有提供等待条件变量等高级功能。

@synchronized

  • 优点
    • 语法简洁,使用 @synchronized(self) 这样的形式即可锁定一段代码。
    • 自动管理锁的生命周期,无需手动调用 lockunlock,减少了忘记解锁的风险。
  • 缺点
    • 性能相对较差,因为它内部实现相对复杂,涉及到对象的散列表操作。
    • 无法设置超时时间,如果等待锁的时间过长,可能会导致线程长时间阻塞。

NSCondition

  • 优点
    • 功能强大,除了基本的锁功能外,还支持条件变量。可以让线程在满足特定条件时才继续执行,适用于线程间需要协调工作的场景。
    • 可以设置等待条件变量的超时时间,避免线程无限期等待。
  • 缺点
    • 相比简单的锁,使用起来较为复杂,需要正确处理锁和条件变量的关系。
    • 由于功能丰富,实现相对复杂,性能上可能不如简单的锁。

2. 避免死锁的预防策略

2.1 资源分配图算法

  • 可以使用资源分配图算法(如银行家算法)来检测和避免死锁。在多线程访问多个资源的场景下,系统可以通过记录每个线程对资源的请求和分配情况,来判断是否会产生死锁。如果一个请求会导致系统进入不安全状态(可能产生死锁),则拒绝该请求。

2.2 按顺序加锁

  • 为所有资源分配一个全局的顺序(例如按照资源的内存地址排序)。当多个线程需要访问多个资源时,所有线程都按照这个顺序来获取锁。这样可以避免循环等待资源的情况,从而避免死锁。

2.3 超时机制

  • 为获取锁设置一个超时时间。如果在超时时间内没有获取到锁,则放弃当前操作,释放已经获取的锁,并等待一段时间后重试。这样可以避免线程无限期等待锁,从而避免死锁。

3. 代码示例

按顺序加锁示例

#import <Foundation/Foundation.h>

// 假设有两个资源
NSLock *resource1Lock = [[NSLock alloc] init];
NSLock *resource2Lock = [[NSLock alloc] init];

// 线程1
dispatch_queue_t queue1 = dispatch_queue_create("com.example.queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
    if ([resource1Lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) {
        if ([resource2Lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) {
            // 访问资源1和资源2
            NSLog(@"Thread 1 accessing resources");
            [resource2Lock unlock];
        }
        [resource1Lock unlock];
    }
});

// 线程2
dispatch_queue_t queue2 = dispatch_queue_create("com.example.queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue2, ^{
    if ([resource1Lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) {
        if ([resource2Lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) {
            // 访问资源1和资源2
            NSLog(@"Thread 2 accessing resources");
            [resource2Lock unlock];
        }
        [resource1Lock unlock];
    }
});

超时机制示例

#import <Foundation/Foundation.h>

NSLock *resourceLock = [[NSLock alloc] init];

dispatch_queue_t queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    if ([resourceLock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) {
        // 访问资源
        NSLog(@"Thread accessing resource");
        [resourceLock unlock];
    } else {
        NSLog(@"Thread timed out waiting for lock");
    }
});