并发编程 - 锁(NSConditionLock)

引言

今天我们来介绍另外一个锁——NSConditionLock当看见这个名字时候,大家可能马上会想到上一篇并发编程的博客NSCondition,但是他们两个并没有直接关系,NSConditionLock也更不是NSCondition的子类,NSConditionLock是一个条件锁,一旦获取锁后,只有当条件满足时才会释放当前的互斥锁。

接下来,我们将深入探讨NSConditionLock的使用场景、方法以及它在多线程编程中的重要性。

NSConditionLock简介

NSConditionLock是一个用于多线程编程的条件锁,旨在协调线程之间的访问和执行。在获取NSConditionLock后,线程只有在特定的条件满足时,才能释放锁,这使得它在复杂的线程管理中极为有效。与传统的互斥锁不同,NSConditionLock允许开发者为锁定的资源定义多个条件,帮助避免死锁和竞争条件的发生。通过利用NSConditionLock,开发者可以实现更灵活和可控的线程同步机制,适用于各种需要精确控制线程执行顺序的场景。

NSConditionLock使用场景

在NSCondition能胜任的场景中NSCondtitionLock也同样能胜任,不仅如此,由于NSCondtitionLock可以根据特定条件来执行任务,因此它的使用场景相对于NSCondition也更广泛。

主要有以下场景:

  1. 生产者-消费者模式:在这个经典的多线程问题中,生产者和消费者需要协调对共享资源的访问。NSConditionLock可以确保在资源可用时,消费者才能获取锁进行消费。
  2. 任务调度:当多个线程需要根据特定条件来执行任务时,NSConditionLock可以帮助确保任务正常的顺序执行。例如,一个线程可能需要等待某个计算结果才能继续处理。
  3. 状态依赖的执行:在某些应用中,线程的执行顺序可能依赖于特定状态的改变。使用NSConditionLock,可以在状态满足时唤醒等待的线程。
  4. 资源共享管理:当多个线程访问共享资源时,NSConditionLock能够根据条件控制对象资源的访问,从而提高效率并降低冲突。

NSConditionLock使用方法

NSConditionLock的核心功能在于其条件控制机制,主要包括以下几个方法:

初始化:

NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];

这里的condition表示初始条件,通常设置为0。

获取锁:

[lock lockWhenCondition:1];

该线程会在条件为1时获取锁,如果条件不满足,线程会被阻塞。

释放锁:

[lock unlockWithCondition:0];

释放锁并设置新的条件。这里将条件重置为0。

设置条件:

[lock unlockWithCondition:2];

在释放锁时可以改变条件,可以通知其他等待的线程。

检查当前条件:

NSInteger currentCondition = [lock condition];

可以通过此方法获取当前的条件状态。

在实际应用中,NSConditionLock长用来控制线程执行的顺序。例如,在一个生产者-消费者模型中,生产者生产完数据后,可以通过调用unlockWithCondition:1来通知消费者线程数据可用。消费者通过lockWhenCondition:1来等待数据准备好,从而安全地获取数据。

NSConditionLock使用示例

生产者消费者模式,我们使用NSConditionLock来更灵活地管理生产和消费的逻辑。

首先来创建一个管理者:

#import "ShareBuffer.h"

@interface ShareBuffer ()

@property (nonatomic, strong) NSMutableArray *buffer;
@property (nonatomic, strong) NSConditionLock *lock;

@end

@implementation ShareBuffer

- (instancetype)init {
    self = [super init];
    if (self) {
        _buffer = [NSMutableArray array];
        _lock = [[NSConditionLock alloc] initWithCondition:0]; // 初始条件为0(空缓冲区)
    }
    return self;
}

- (void)produce {
    for (int i = 0; i < 10; i++) {
        [self.lock lockWhenCondition:0]; // 只有当缓冲区为空时才能生产
        [self.buffer addObject:@(i)];
        NSLog(@"Produced: %d", i);
        [self.lock unlockWithCondition:1]; // 设置条件为1(缓冲区有内容),通知消费者
    }
}

- (void)consume {
    for (int i = 0; i < 10; i++) {
        [self.lock lockWhenCondition:1]; // 只有当缓冲区有内容时才能消费
        NSNumber *item = [self.buffer firstObject];
        [self.buffer removeObjectAtIndex:0];
        NSLog(@"Consumed: %@", item);
        [self.lock unlockWithCondition:0]; // 设置条件为0(缓冲区空),通知生产者
    }
}

@end

开启生产队列来生产产品,一个消费队列来消费产品:

- (void)textConditionLock {
    ShareBuffer *sharedBuffer = [[ShareBuffer alloc] init];
    
    dispatch_queue_t producerQueue = dispatch_queue_create("producerQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t consumerQueue = dispatch_queue_create("consumerQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(producerQueue, ^{
        [sharedBuffer produce];
    });
    
    dispatch_async(consumerQueue, ^{
        [sharedBuffer consume];
    });
}

在这个示例中我们引入了三个条件来更灵活地管理生产者-消费者模式:

1.条件0(缓冲区为空):

生产者在缓存区为空时进入生产逻辑,生产一个项后,更新条件为1,通知消费者有新想可用。

2.条件1(缓冲区有内容)
消费者在缓冲区有内容是进行消费,消费一项后,更新条件为0,通知生产者缓冲区已空。

我们再来看另外一个更直观的例子:

- (void)textConditionLock2 {
    // 1. 创建一个 NSConditionLock 对象,并设置初始条件为 2
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    
    // 2. 创建 3 个线程
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"线程 1");
        [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        [conditionLock lockWhenCondition:2];
        NSLog(@"线程 2");
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       NSLog(@"线程 3");
       [conditionLock unlock];
    });
}

运行代码结果如下:

线程 3

线程 2

线程 1

其实线程3这个输出项我们并不需要关心,它不依赖任何条件,直接获取锁,而代码执行结束后直接释放锁。所以它的输出位置可能在任何地方。

而对于线程2和线程1而言,由于NSConditionLock的初始条件为2,所以只有线程2可以获取锁,而在线程2执行结束之后会更新条件为1,此时线程1才会获取锁执行代码。

NSConditionLock总结

NSConditionLock是一种锁机制,一旦一个线程获得了锁,其他线程必须等待。

[xxx lock]:表示该线程期望获得锁。如果没有其他现场持有该锁(无论是条件锁还是无条件锁),它将执行锁后面的代码。如果已经有其他线程持有锁,该线程会等待,直到其他线程释放锁。

[xxx lockWhenCondition:条件A]:表示该现场期望在条件为A时获得锁。如果没有其他线程持有该锁,但当前条件不等于A,该线程将会等待。只有当条件等于A并且没有其他线程持有锁时,它才能进入代码区,并成功获得锁。此时,其他任何线程都将等待,直到该线程释放锁。

[xxx unlockWithCondition:条件A]:表示释放锁的同时,将内部条件置为A。这允许其他等待的线程在条件满足时获得锁。

结语

在多线程编程中,NSConditionLock提供了一种强大而灵活的方式来控制线程的执行顺序。通过使用条件锁,我们可以精确地管理线程之间的同步,确保共享资源的安全访问。与其他同步机制相比,条件锁的优势在于它允许线程根据特定条件来决定是否获取锁,从而提高了程序的效率和响应能力。在实际开发中,合理运用NSConditionLock可以显著降低线程冲突和死锁的风险,为构建高效、安全的并发应用奠定基础。通过本篇文章的介绍,希望能帮助你更好地理解和应用NSConditionLock,提升你的并发编程技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值