iOS-机制

iOS-锁-NSCondition&NSConditionLoc

2020-03-13  本文已影响0人  xxxxxxxx_123

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);
}

运行程序,我们发现控制台的输出是有问题的:

image

此时,我们就可以使用条件锁解决问题。我们只需要对程序作如下改动就可以正常执行:

- (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,就不得不说一下NSConditionLockNSConditionLockNSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用。

我们来看看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,唤醒当前的线程。

总结

相同点:

不同点:

上一篇下一篇

猜你喜欢

热点阅读