条件锁介绍
应用
源码
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 可以随时使线程进入休眠或被唤醒。