MST

星途 面试题库

面试题:Objective-C中如何使用NSCondition实现复杂的线程同步场景

假设你有一个多线程应用场景,其中一个线程负责生产数据并放入队列,另外多个线程负责从队列消费数据。但要求消费线程只有在队列中有足够数据(比如至少3个数据)时才进行消费,同时要避免数据竞争和死锁。请使用NSCondition来实现这个复杂的线程同步场景,并解释关键代码的作用。
44.4万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

以下是使用NSCondition实现该线程同步场景的Objective-C代码示例及关键代码解释:

#import <Foundation/Foundation.h>

@interface DataQueue : NSObject

@property (nonatomic, strong) NSMutableArray *queue;
@property (nonatomic, strong) NSCondition *condition;

- (void)produceData:(id)data;
- (NSArray *)consumeData;

@end

@implementation DataQueue

- (instancetype)init {
    self = [super init];
    if (self) {
        _queue = [NSMutableArray array];
        _condition = [[NSCondition alloc] init];
    }
    return self;
}

- (void)produceData:(id)data {
    [self.condition lock];
    [self.queue addObject:data];
    NSLog(@"Produced: %@", data);
    [self.condition signal];
    [self.condition unlock];
}

- (NSArray *)consumeData {
    [self.condition lock];
    while (self.queue.count < 3) {
        [self.condition wait];
    }
    NSArray *consumedArray = [self.queue subarrayWithRange:NSMakeRange(0, 3)];
    [self.queue removeObjectsInRange:NSMakeRange(0, 3)];
    NSLog(@"Consumed: %@", consumedArray);
    [self.condition unlock];
    return consumedArray;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DataQueue *dataQueue = [[DataQueue alloc] init];
        
        dispatch_queue_t producerQueue = dispatch_queue_create("com.example.producerQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t consumerQueue = dispatch_queue_create("com.example.consumerQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(producerQueue, ^{
            for (int i = 0; i < 10; ++i) {
                NSString *data = [NSString stringWithFormat:@"Data %d", i];
                [dataQueue produceData:data];
                sleep(1);
            }
        });
        
        dispatch_async(consumerQueue, ^{
            while (true) {
                NSArray *consumed = [dataQueue consumeData];
                if (consumed.count == 0) {
                    break;
                }
                sleep(2);
            }
        });
        
        sleep(15);
    }
    return 0;
}

关键代码解释

  1. 初始化部分

    - (instancetype)init {
        self = [super init];
        if (self) {
            _queue = [NSMutableArray array];
            _condition = [[NSCondition alloc] init];
        }
        return self;
    }
    
    • 初始化NSMutableArray用于存储数据,初始化NSCondition用于线程同步。
  2. 生产数据方法produceData:

    - (void)produceData:(id)data {
        [self.condition lock];
        [self.queue addObject:data];
        NSLog(@"Produced: %@", data);
        [self.condition signal];
        [self.condition unlock];
    }
    
    • [self.condition lock];:锁定NSCondition,确保对_queue的操作是线程安全的,避免数据竞争。
    • [self.queue addObject:data];:将生产的数据添加到队列中。
    • [self.condition signal];:发送信号,通知等待在NSCondition上的消费线程队列中有新数据。
    • [self.condition unlock];:解锁NSCondition,允许其他线程操作。
  3. 消费数据方法consumeData

    - (NSArray *)consumeData {
        [self.condition lock];
        while (self.queue.count < 3) {
            [self.condition wait];
        }
        NSArray *consumedArray = [self.queue subarrayWithRange:NSMakeRange(0, 3)];
        [self.queue removeObjectsInRange:NSMakeRange(0, 3)];
        NSLog(@"Consumed: %@", consumedArray);
        [self.condition unlock];
        return consumedArray;
    }
    
    • [self.condition lock];:锁定NSCondition,确保对_queue的操作是线程安全的。
    • while (self.queue.count < 3) { [self.condition wait]; }:使用while循环检查队列中的数据是否足够(至少3个),如果不足则调用[self.condition wait];,使当前线程等待,同时释放NSCondition的锁,允许其他线程操作队列。当其他线程调用signalbroadcast时,此线程被唤醒,重新获取锁并检查条件。这一步避免了死锁,因为线程在等待时会释放锁。
    • NSArray *consumedArray = [self.queue subarrayWithRange:NSMakeRange(0, 3)];[self.queue removeObjectsInRange:NSMakeRange(0, 3)];:从队列中取出并移除前3个数据。
    • [self.condition unlock];:解锁NSCondition,允许其他线程操作。
    • return consumedArray;:返回消费的数据。
  4. 主函数中的多线程调度

    dispatch_queue_t producerQueue = dispatch_queue_create("com.example.producerQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t consumerQueue = dispatch_queue_create("com.example.consumerQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(producerQueue, ^{
        for (int i = 0; i < 10; ++i) {
            NSString *data = [NSString stringWithFormat:@"Data %d", i];
            [dataQueue produceData:data];
            sleep(1);
        }
    });
    
    dispatch_async(consumerQueue, ^{
        while (true) {
            NSArray *consumed = [dataQueue consumeData];
            if (consumed.count == 0) {
                break;
            }
            sleep(2);
        }
    });
    
    • 创建生产者和消费者队列,并在队列中异步执行生产和消费任务。生产者循环生产10个数据,消费者持续消费数据,直到没有足够数据可消费。sleep函数用于模拟实际的生产和消费延迟。

通过上述实现,利用NSCondition实现了线程同步,避免了数据竞争和死锁,满足了题目要求的复杂线程同步场景。