35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock

条件锁 NSConditionLock

1、定义

条件锁就是有特定条件的锁,所谓条件只是一个抽象概念,由程序猿自定义。说白了就是「有条件的互斥锁」.对于NSConditionLock,官方文档的描述是这样的:

使用NSConditionLock对象,可以确保线程仅在满足特定条件时才能获取锁。 一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。 条件本身是任意的:您可以根据应用程序的需要定义它们。

NSConditionLock 同样实现了 NSLocking 协议,并定义了一些特定的接口:

@interface NSConditionLock : NSObject  {
@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

可以看到,所谓的条件condition就是个NSInteger,没有其他任何限制,使用起来就像UIViewtag一样。类的定义中,不带condition 的和 NSLock 没什么区别。特有的:

  • 只读属性condition,保存锁当前的条件
  • -lockWhenCondition::获取锁,如果condition与属性相等,则可以获得锁,否则阻塞线程,等待被唤醒
  • -unlockWithCondition:释放锁,并修改condition属性

 

 2、条件锁汇编分析

-(void)ljl_testCondition2
{
//    类似于信号量
    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];
    });
}

//****************************************************
2020-04-06 21:45:38.273772+0800 filedome[1802:45978] 线程2
2020-04-06 21:45:38.274064+0800 filedome[1802:45870] 线程1
2020-04-06 21:45:38.274198+0800 filedome[1802:45871] 线程3

线程3 和 线程2谁先执行不一定,但是线程1一定是在线程2之后执行的。

执行线程1、2、3(顺序不确定的)

但线程1 、2获取锁需要看 condition 是否满足

  • 线程3  获取锁不需要条件,先获取到锁,开始执行任务(不一定是最先执行的)
  • 线程2  因为条件是2,所以线程2可以执行。当线程2释放锁时将condition置为1,满足线程1的条件,此时线程1获取到锁执行任务
  • 线程1  释放锁时将condition置为0,不再执行任务

在 [conditionLock lockWhenCondition:1]; 打断点。汇编查看
 35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第1张图片
 

然后下符号断点 lockWhenCondition:   + -> Symbolic BreakPoint... -> -[NSConditionLock lockWhenCondition:]
然后下符号断点 unlockWhenCondition:   + -> Symbolic BreakPoint... -> -[NSConditionLock unlockWhenCondition:]

 
35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第2张图片

 这个date 哪来的干什么的不知道 看一下文档。 xcode中选中 lockWhenCondition -> Help -> Search Documentation for Selected Text
 
 - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
 被锁的话此方法将阻塞线程的执行,直到解锁或超时为止。
 35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第3张图片
 通过调试,发现NSConditionLock 是对 NSCondition 的封装。通过加锁来解决线程完全问题,那么条件是怎么什么地方处理的呢?
 35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第4张图片35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第5张图片

 

最后发现有调用了 waitUntilDate: 这个跟  [_testCondition wait]; 是一样的,条件不一样的时候进行等待。

35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第6张图片

35、iOS底层分析 - 线程锁(四)条件锁 NSconditionLock_第7张图片
 执行完调用 broadcast 进行广播
 

即使在未来某个时间点可以满足条件,但-tryLockWhenCondition:只是根据当前condition获取锁

无论能否获取到锁,该线程都会继续向下执行,不会阻塞

 

3、应用

    //主线程中
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"线程1");
        sleep(2);
        [lock unlock];
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:0]) {
            NSLog(@"线程2");
            [lock unlockWithCondition:2];
            NSLog(@"线程2解锁成功");
        } else {
            NSLog(@"线程2尝试加锁失败");
        }
    });

    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"线程3");
            [lock unlock];
            NSLog(@"线程3解锁成功");
        } else {
            NSLog(@"线程3尝试加锁失败");
        }
    });

    //线程4
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"线程4");
            [lock unlockWithCondition:1];    
            NSLog(@"线程4解锁成功");
        } else {
            NSLog(@"线程4尝试加锁失败");
        }
    });

2016-08-19 13:51:15.353 ThreadLockControlDemo[1614:110697] 线程2
2016-08-19 13:51:15.354 ThreadLockControlDemo[1614:110697] 线程2解锁成功
2016-08-19 13:51:16.353 ThreadLockControlDemo[1614:110689] 线程3
2016-08-19 13:51:16.353 ThreadLockControlDemo[1614:110689] 线程3解锁成功
2016-08-19 13:51:17.354 ThreadLockControlDemo[1614:110884] 线程4
2016-08-19 13:51:17.355 ThreadLockControlDemo[1614:110884] 线程4解锁成功
2016-08-19 13:51:17.355 ThreadLockControlDemo[1614:110884] 线程1

上面代码先输出了 ”线程 2“,因为线程 1 的加锁条件不满足,初始化时候的 condition 参数为 0,而加锁条件是 condition 为 1,所以加锁失败。locakWhenCondition 与 lock 方法类似,加锁失败会阻塞线程,所以线程 1 会被阻塞着,而 tryLockWhenCondition 方法就算条件不满足,也会返回 NO,不会阻塞当前线程。

回到上面的代码,线程 2 执行了 [lock unlockWithCondition:2]; 所以 Condition 被修改成了 2。

而线程 3 的加锁条件是 Condition 为 2, 所以线程 3 才能加锁成功,线程 3 执行了 [lock unlock]; 解锁成功且不改变 Condition 值。

线程 4 的条件也是 2,所以也加锁成功,解锁时将 Condition 改成 1。这个时候线程 1 终于可以加锁成功,解除了阻塞。

从上面可以得出,NSConditionLock 还可以实现任务之间的依赖。

 

4、注意事项

  • 由于NSConditionLock也实现了NSLocking协议,可以通过-lock加锁以及-unlock解锁,但无论当前condition是多少,-lock都可以获得锁,-unlock也不会改变当前condition的值。
  • 由于是同一把锁,同一时间只能由一个线程持有,如果当前condition满足线程1的持锁条件,但线程2通过-lock获取没有条件的锁,此时他们谁先抢到锁就执行谁的任务,另一个就要等锁释放。
  • 通过-lockWhenCondition:获取锁时,线程会休眠等待,必须有其他线程设置为合适的条件,否则该线程永远获取不到锁

熟悉了条件锁NSConditionLock的使用方法,可以用一个任务编号(条件)来指定不同线程任务的执行顺序。

 

6.源码

先看NSConditionLock的属性:

open class NSConditionLock : NSObject, NSLocking {
    internal var _cond = NSCondition()
    internal var _value: Int
    internal var _thread: _swift_CFThreadRef?
}
  • _cond:一个NSCondition对象
  • _value:记录当前条件的值
  • _thread:记录当前线程

无论是-lock还是-lockWhenCondition:都会走这个函数:

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
}
  • 如果当前条件和condition不相等,则再一次休眠等待,否则可以获得锁

释放条件锁:

open func unlock(withCondition condition: Int) {
    _cond.lock()
    _thread = nil
    _value = condition
    _cond.broadcast()
    _cond.unlock()
}
  • 将当前条件更新,并broadcast()唤醒所有等待在这个锁的线程

 

 

 

 

你可能感兴趣的:(iOS底层分析)