34、iOS底层分析 - 线程锁(三)条件锁 NSCondition

大纲

  1. 条件锁介绍
  2. 应用
  3. 源码

NSCondition

条件锁

条件锁我们调用wait方法就把当前线程进入等待状态,当调用了signal方法就可以让该线程继续执行,也可以调用broadcast广播方法。


 NSCondition 和 NSLock 的区别
 NSLock:在获取不到锁的时候自动使线程进入休眠,锁被释放后线程自动被唤醒
 NSCondition:可以使我们更加灵活的控制线程状态,在任何需要的时候使线程进入休眠或唤醒它
 
 NSCondition 遵循 NSLocking 协议

 @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

 -wait                   阻塞当期线程,使线程进入休眠,等待唤醒信号。调用前必须已加锁 -lock
 -waitUntilDate 阻塞当前线程,线程进入休眠,等待唤醒信号或者超时。如果是被信号唤醒返回YES,否者返回NO。调用前必须已加锁 -lock
 -signal              唤醒一个正在休眠的线程,如果要唤醒多个线程,需要调用多次,如果没有线程在等待,什么也不做。调用前必须已加锁 -lock
 -broadcast      唤醒所有在等待的线程,如果没有线程在等待,什么也不做。调用前必须已加锁 -lock
 
 Condition 是条件,条件是我们自己决定的。


 应用

@property(nonatomic, strong) NSCondition * testCondition;
@property(nonatomic, assign) NSUInteger ticketCount;

@end

@implementation LJLLockViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketCount = 0;
    [self ljl_testCondition];
}
-(void)ljl_testCondition
{
    _testCondition = [[NSCondition alloc] init];
//    创建生产 - 消费者
    for (int i=0; i<50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self ljl_producer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self ljl_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self ljl_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self ljl_producer];
        });
    }
}

-(void)ljl_producer
{
    [_testCondition lock];
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生产一个 现有 count %zd",self.ticketCount);
    [_testCondition signal];//信号,生产了一个通知可以消费了
    [_testCondition unlock];//解锁
}

-(void)ljl_consumer
{
//    线程安全
    [_testCondition lock];
    
//     if (self.ticketCount == 0) {
    while (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
//        保证正常流程
        [_testCondition wait];//等待  剩余的count == 0 不能再消费所以要等待
    }
    
//    注意消费行为,要等待条件判断之后
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd",self.ticketCount);
    [_testCondition unlock];
}

 if 不安全,因为有多线程所以并不能卡住所有的判断条件。同时也没加锁的情况下 就可以看到先出现了消费1个还剩0个,案例说 ==0了应该卡住等待,结果有打印了1

 2020-04-04 21:09:33.386969+0800 filedome[47414:1674928] 生产一个 现有 count 1
 2020-04-04 21:09:33.387002+0800 filedome[47414:1674486] 生产一个 现有 count 2
 2020-04-04 21:09:33.387111+0800 filedome[47414:1674928] 消费一个 还剩 count 0
 2020-04-04 21:09:33.387111+0800 filedome[47414:1674927] 消费一个 还剩 count 1
 2020-04-04 21:09:33.387116+0800 filedome[47414:1674926] 等待 count 0

换成while 虽然能卡住但是 == 0,但是不能处理多线程同时访问的问题。

 2020-04-04 21:11:54.433800+0800 filedome[47526:1676373] 消费一个 还剩 count 0
 2020-04-04 21:11:54.498035+0800 filedome[47526:1676429] 消费一个 还剩 count 0
 2020-04-04 21:11:54.498046+0800 filedome[47526:1676385] 生产一个 现有 count 1
 2020-04-04 21:11:54.498198+0800 filedome[47526:1676387] 生产一个 现有 count 2


 加条件锁 NSCondition
 ticketCount == 0 的时候休眠 (-wait),ticketCount>0 的时候发信号唤醒线程进行执行 (-signal)
 

 源码

 NSCondition 源码。其中使用了一个pthread_mutex_t 的互斥锁(同NSLock),还使用了 pthread_cond_t 的条件共同实现。

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

NSCondition 就相当于 NSLock + Condition,通过condition 可以随时使线程进入休眠或被唤醒。

发布了104 篇原创文章 · 获赞 13 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/105373345