NSCondition
条件锁,顾名思义,就是满足某些条件才会开锁。NSCondition
,可以确保线程仅在满足特定条件时才能获取锁。一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。条件本身是任意的:可以根据应用程序的需要定义它们。
NSCondition
对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。通俗的说,也就是条件成立,才会执行锁住的代码。条件不成立时,线程就会阻塞,直到另一个线程向条件对象发出信号解锁为止。
下面我们看一个例子:
- (void)conditionTest {
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self addTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self addTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self minusTickets];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self minusTickets];
});
}
}
- (void)addTickets {
self.ticketCount += 1;
NSLog(@"加一个现有ticketCount==%zd",self.ticketCount);
}
- (void)minusTickets {
while (self.ticketCount == 0) {
NSLog(@"==没有ticketCount==");
return;
}
self.ticketCount -= 1;
NSLog(@"减一个剩下ticketCount==%zd",self.ticketCount);
}
运行程序,我们发现控制台的输出是有问题的:
此时,我们就可以使用条件锁解决问题。我们只需要对程序作如下改动就可以正常执行:
- (void)addTickets {
[self.condition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"现有ticketCount==%zd",self.ticketCount);
[self.condition unlock];
[self.condition signal];
}
- (void)minusTickets {
[self.condition lock];
while (self.ticketCount == 0) {
NSLog(@"==没有ticketCount==");
[self.condition wait];
return;
}
self.ticketCount -= 1;
NSLog(@"减一个,剩下ticketCount==%zd",self.ticketCount);
[self.condition unlock];
}
那么NSCondition
到底做了什么呢?我们来看看源码,然而,Objective-C
代码并不能看到NSCondition
的具体实现:
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
需要使用swift
源码进行查看:
open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
deinit {
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
}
// 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,
// 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
open func lock() {
pthread_mutex_lock(mutex)
}
// 释放锁,与lock成对出现
open func unlock() {
pthread_mutex_unlock(mutex)
}
// 让当前线程处于等待状态,阻塞
open func wait() {
pthread_cond_wait(cond, mutex)
}
// 让当前线程等待到某个时间,阻塞
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
// 发信号告诉线程可以继续执行,唤醒线程
open func signal() {
pthread_cond_signal(cond)
}
open func broadcast() {
pthread_cond_broadcast(cond) // wait signal
}
open var name: String?
}
可以看到,该对象还是对pthread_mutex
的一层封装,NSCondition
也是一种互斥锁。当我们需要等待某个条件的时候,也就是条件不满足的时候,就可以使用wait
方法来阻塞线程,当条件满足了,使用signal
方法发送信号唤醒线程。
NSConditionLock
说到NSCondition
,就不得不说一下NSConditionLock
,NSConditionLock
对NSCondition
又做了一层封装,自带条件探测,能够更简单灵活的使用。
我们来看看NSConditionLock
的相关源码:
Objective-C
下,只能看到方法的定义,并不能看到实现:
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
我们使用swift
查看一下:
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
// 表示 xxx 期待获得锁,
// 如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,
// 如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
// 表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。
// 如果内部的condition等于A条件,并且没有其他线程获得该锁,则执行任务,同时设置它获得该锁
// 其他任何线程都将等待它代码的完成,直至它解锁。
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distantPast)
}
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
// 表示释放锁,同时把内部的condition设置为A条件
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。
// 需要注意的是:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open var name: String?
可以看出,触发的唤醒线程的条件是传入的condition
取值,和我们创建锁的时候值要相同,我们可以在释放当前线程锁的时候重新设置其他线程传入的condition
值,这样也就达到了唤醒其他线程的目的。如果创建锁的值和传入的值都不能匹配,则会进入阻塞状态。
下面我们来看个例子:
- (void)conditionLockTest {
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
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];
});
}
线程1调用[NSConditionLock lockWhenCondition:]
,此时此刻因为不满足当前条件,所以会进入等待状态。此时当前的线程3
调用[NSConditionLock lock:]
,本质上是调用 [NSConditionLock lockBeforeDate:]
,这里不需要比对条件值,所以线程 3会打印。接下来线程2执行[NSConditionLock lockWhenCondition:]
,因为满足条件值,所以线程2会打印,打印完成后会调用[NSConditionLock unlockWithCondition:]
,这个时候将条件设置为 1,并发送boradcast
, 此时线程1接收到当前的信号,唤醒执行并打印。
自此当前打印为 线程 3->线程 2 -> 线程 1。
[NSConditionLock lockWhenCondition:]
:这里会根据传入的condition
值和value
值进行对比,如果不相等,这里就会阻塞。而[NSConditionLock unlockWithCondition:]
会先更改当前的value
值,然后调用boradcast
,唤醒当前的线程。
总结
相同点:
- 都是互斥锁
- 通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的
不同点:
-
NSCondition
是基于对pthread_mutex
的封装,而NSConditionLock
是对NSCondition
做了一层封装 -
NSCondition
是需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,NSConditionLock
则只需要外部传入一个值,就会依据这个值进行自动判断是阻塞线程还是唤醒线程