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

条件锁介绍
应用
源码

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 可以随时使线程进入休眠或被唤醒。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。