并发编程 - 锁(NSCondition)

引言

在之前的并发编程博客中,我们已经介绍了多种锁机制,以确保线程安全,包括属性修饰符Atomic、@synchronized代码块、轻量级的NSLock和递归锁NSRecursiveLock。虽然这些锁的用法各有不同,但它们的核心目的相同:确保在同一时刻,只有一条线程能够访问被加锁的代码。

今天,我们将来深入探讨另一种锁类型——NSCondition。

NSCondition 概述

NSCondition的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。

NSCondition是一种用于实现线程同步的锁机制,主要用于在多线程环境中协调线程之间的执行。与其他锁(如NSLock和@synchronized)相比,NSCondition提供了更为灵活的线程等待和通知机制。

特性

条件变量:NSCondition运行线程在某些条件不满足时进入等待状态,并在条件满足时被其他线程通知。这种机制非常适合实现复杂的生产者-消费者模式。

优先级控制:通过使用signal()和broadcast()方法,开发者可以灵活地扩中那个线程被唤醒。

资源效率:相比于简单的互斥锁,NSCondition更适合需要等待特定条件的场景,避免了不必要的CPU占用。

与其他锁的比较

  1. NSLock和NSRecursiveLock主要用于保护共享资源,而NSCondition更加关注线程之间的协调。
  2. NSCondition提供了内置的条件等待与通知机制,使其在某些场景下更加高效和易用。

基本用法

相比于其他说来说,NSCondition的用法要复杂一些,因为它除了实现线程同步以外还需要进行条件控制,以下是使用NSCondition的基本步骤和示例代码。

1.初始化NSCondition

在使用NSCondition之前,需要先创建一个实例:

NSCondition *condition = [[NSCondition alloc] init];

2.加锁与解锁

使用lock()方法获取锁,确保只有一个线程能够执行被保护的代码块;使用unlock方法释放锁:

[condition lock];
// 执行需要保护的代码
[condition unlock];

3.等待与通知

使用wait()方法使当前线程进入等待状态,直到其他线程调用signal()或broadcast()方法:

[condition lock];

// 当条件不满足时,等待
while (!conditionMet) {
    [condition wait];
}

// 处理条件满足后的代码
[condition unlock];

使用signal()方法通知一个等待的线程,使用broadcast()方法通知所有等待的线程:

// 通知一个线程
[condition signal];

// 通知所有线程
[condition broadcast];

实际使用场景

NSCondition的一个最常见的应用场景是生产者-消费者模型。在这个模型中,生产者负责生成数据并将其放入缓冲区,而消费者则从缓冲区中取出数据进行处理。通过NSCondition,可以有效地管理线程之间的协调与同步,确保数据安全地在生产者和消费者之间传递。

未使用NSCondition的生产消费模型

我们先来看一下在不使用NSCondition情况下的示例:

开启多个全局并发队列调用生成和消费的方法

//MARK: NSCondition
- (void)testCondition {
    self.maxCount = 10;
    self.productCount = 0;
    self.condition = [[NSCondition alloc] init];
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self produce];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self consume];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self consume];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self produce];
        });
    }
}

生产方法

//MARK: 生产者
- (void)produce {
    self.productCount++;
    NSLog(@"生产者生产了一个产品,当前产品数量:%ld",self.productCount);
}

消费方法

//MARK: 消费者
- (void)consume {
    self.productCount--;
    NSLog(@"消费者消费了一个产品,当前产品数量:%ld",self.productCount);
}

执行代码后结果如下:

生产者生产了一个产品,当前产品数量:0

生产者生产了一个产品,当前产品数量:1

生产者生产了一个产品,当前产品数量:0

生产者生产了一个产品,当前产品数量:1

消费者消费了一个产品,当前产品数量:-1

生产者生产了一个产品,当前产品数量:1

生产者生产了一个产品,当前产品数量:0

消费者消费了一个产品,当前产品数量:0

生产者生产了一个产品,当前产品数量:0

消费者消费了一个产品,当前产品数量:0

消费者消费了一个产品,当前产品数量:-1

生产者生产了一个产品,当前产品数量:1

可以看到现象很奇怪,明明生产了一个产品,但是当前数量却为0,甚至有的时候消费后产品数量为负数,这明显出现了问题。

使用NSCondition的生产消费模型

以下是使用NSCondition实现生产者-消费者的示例:

  • 生产者线程:负责生成数据并放入缓存区。当缓冲区满时,生产者会等待。
  • 消费者线程:负责从缓冲区取出数据进行处理。当缓冲区为空时,消费者会等待。

调用的代码相同,我们只需要修改生产和消费的方法。

生产方法

//MARK: 生产者
- (void)produce {
    [self.condition lock];
    while (self.productCount >= self.maxCount) {
        NSLog(@"产品已满");
        [self.condition wait];
    }
    self.productCount++;
    NSLog(@"生产者生产了一个产品,当前产品数量:%ld",self.productCount);
    [self.condition signal];
    [self.condition unlock];
}

消费方法

//MARK: 消费者
- (void)consume {
    [self.condition lock];
    while (self.productCount <= 0) {
        NSLog(@"产品已空");
        [self.condition wait];
    }
    self.productCount--;
    NSLog(@"消费者消费了一个产品,当前产品数量:%ld",self.productCount);
    [self.condition signal];
    [self.condition unlock];
}

执行结果如下:

生产者生产了一个产品,当前产品数量:1

消费者消费了一个产品,当前产品数量:0

生产者生产了一个产品,当前产品数量:1

生产者生产了一个产品,当前产品数量:2

消费者消费了一个产品,当前产品数量:1

生产者生产了一个产品,当前产品数量:2

生产者生产了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

生产者生产了一个产品,当前产品数量:5

消费者消费了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

生产者生产了一个产品,当前产品数量:5

消费者消费了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

生产者生产了一个产品,当前产品数量:4

生产者生产了一个产品,当前产品数量:5

消费者消费了一个产品,当前产品数量:4

消费者消费了一个产品,当前产品数量:3

消费者消费了一个产品,当前产品数量:2

生产者生产了一个产品,当前产品数量:3

消费者消费了一个产品,当前产品数量:2

消费者消费了一个产品,当前产品数量:1

消费者消费了一个产品,当前产品数量:0

生产者生产了一个产品,当前产品数量:1

消费者消费了一个产品,当前产品数量:0

产品已空

生产者生产了一个产品,当前产品数量:1

消费者消费了一个产品,当前产品数量:0

可以看到每生产一个就会多一个产品,而消费一个就会少一个产品,当已经没有产品再次消费时,消费线程会进入等待,直到生产线程生产出新的产品,消费线程继续执行。

只是我们没有触发已经生产10个产品,生产线程进入等待的场景。

NSCondition源码

NSCondition的实现依赖于底层的线程同步机制,其源码展示了如何利用互斥锁和条件变量来实现高效的线程控制。下面是NSCondition的核心代码解析:

1.初始化

在init方法中,NSCondition会根据平台选择合适的初始化方式:

public override init() {
#if os(Windows)
    InitializeSRWLock(mutex)
    InitializeConditionVariable(cond)
#else
    pthread_mutex_init(mutex, nil)
    pthread_cond_init(cond, nil)
#endif
}

在这里,Windows平台使用了SRWLock和条件变量,而其他平台使用了POSIX的互斥锁和条件变量。

2.释放资源

在deinit方法中,NSCondition会清理分配的资源:

deinit {
#if os(Windows)
    // SRWLock do not need to be explicitly destroyed
#else
    pthread_mutex_destroy(mutex)
    pthread_cond_destroy(cond)
#endif
    mutex.deinitialize(count: 1)
    cond.deinitialize(count: 1)
    mutex.deallocate()
    cond.deallocate()
}

这确保了在锁对象销毁时,所有资源都被正确释放。

3.锁的操作

lock和unlock方法提供了对互斥锁的控制:

open func lock() {
#if os(Windows)
    AcquireSRWLockExclusive(mutex)
#else
    pthread_mutex_lock(mutex)
#endif
}

open func unlock() {
#if os(Windows)
    ReleaseSRWLockExclusive(mutex)
#else
    pthread_mutex_unlock(mutex)
#endif
}

这些方法确保了线程安全地方法共享资源。

4.等待与通知

wait方法用于让氮气概念线程在条件不满足时进入等待状态:

open func wait() {
#if os(Windows)
    SleepConditionVariableSRW(cond, mutex, WinSDK.INFINITE, 0)
#else
    pthread_cond_wait(cond, mutex)
#endif
}

这使得NSCondition能够有效地处理条件同步。

signal方法用于唤醒一个在等待条件的线程:

open func signal() {
#if os(Windows)
    WakeConditionVariable(cond)
#else
    pthread_cond_signal(cond)
#endif
}

当调用这个方法时,如果有一个线程正在等待条件变量,它将被唤醒并继续执行。

broadcast方法用于唤醒所有在等待条件的线程:

open func broadcast() {
#if os(Windows)
    WakeAllConditionVariable(cond)
#else
    pthread_cond_broadcast(cond)
#endif
}

当调用这个方法时,所有等待的线程都会被唤醒。这个方法适用于当条件变化时,需要通知所有等待线程的场景。

结语

在本篇博客中,我们深入探讨了NSCondition的使用及其在并发编程中的重要性。作为一种高效的线程同步机制,NSCondition不仅能够确保在多线程环境中数据的一致性,还能有效地管理线程之间的协调与通信。

通过生产者-消费者模型的实际示例,我们展示了如何利用NSCondition的lock、unlock、wait、signal和broadcast方法,实现线程间的顺畅交互。这种机制为处理复杂的并发任务提供了强大的支持。

然而,在使用NSCondition时,也要注意避免所和确保正确的条件判断。通过合理的设计和实现我们可以最大程度地发挥NSCondition的优势,使得多线程编程更加高效和安全。

希望通过本篇文章,能够帮助大家更好地理解和运用NSCondition,在iOS开发中提升并发编程的能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值