條件鎖介紹
應(yīng)用
源碼
NSCondition
條件鎖
條件鎖我們調(diào)用wait方法就把當(dāng)前線程進(jìn)入等待狀態(tài),當(dāng)調(diào)用了signal方法就可以讓該線程繼續(xù)執(zhí)行,也可以調(diào)用broadcast廣播方法。
NSCondition 和 NSLock 的區(qū)別
NSLock:在獲取不到鎖的時(shí)候自動(dòng)使線程進(jìn)入休眠,鎖被釋放后線程自動(dòng)被喚醒
NSCondition:可以使我們更加靈活的控制線程狀態(tài),在任何需要的時(shí)候使線程進(jìn)入休眠或喚醒它
NSCondition 遵循 NSLocking 協(xié)議
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;//休眠
- (BOOL)waitUntilDate:(NSDate *)limit;//休眠到什么時(shí)間
- (void)signal;//發(fā)送信號(hào)喚醒鎖
- (void)broadcast;//廣播喚醒鎖
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
-wait 阻塞當(dāng)期線程,使線程進(jìn)入休眠,等待喚醒信號(hào)。調(diào)用前必須已加鎖 -lock
-waitUntilDate 阻塞當(dāng)前線程,線程進(jìn)入休眠,等待喚醒信號(hào)或者超時(shí)。如果是被信號(hào)喚醒返回YES,否者返回NO。調(diào)用前必須已加鎖 -lock
-signal 喚醒一個(gè)正在休眠的線程,如果要喚醒多個(gè)線程,需要調(diào)用多次,如果沒有線程在等待,什么也不做。調(diào)用前必須已加鎖 -lock
-broadcast 喚醒所有在等待的線程,如果沒有線程在等待,什么也不做。調(diào)用前必須已加鎖 -lock
Condition 是條件,條件是我們自己決定的。
應(yīng)用
@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];
// 創(chuàng)建生產(chǎn) - 消費(fèi)者
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(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal];//信號(hào),生產(chǎn)了一個(gè)通知可以消費(fèi)了
[_testCondition unlock];//解鎖
}
-(void)ljl_consumer
{
// 線程安全
[_testCondition lock];
// if (self.ticketCount == 0) {
while (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
// 保證正常流程
[_testCondition wait];//等待 剩余的count == 0 不能再消費(fèi)所以要等待
}
// 注意消費(fèi)行為,要等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費(fèi)一個(gè) 還剩 count %zd",self.ticketCount);
[_testCondition unlock];
}
if 不安全,因?yàn)橛卸嗑€程所以并不能卡住所有的判斷條件。同時(shí)也沒加鎖的情況下 就可以看到先出現(xiàn)了消費(fèi)1個(gè)還剩0個(gè),案例說 ==0了應(yīng)該卡住等待,結(jié)果有打印了1
2020-04-04 21:09:33.386969+0800 filedome[47414:1674928] 生產(chǎn)一個(gè) 現(xiàn)有 count 1
2020-04-04 21:09:33.387002+0800 filedome[47414:1674486] 生產(chǎn)一個(gè) 現(xiàn)有 count 2
2020-04-04 21:09:33.387111+0800 filedome[47414:1674928] 消費(fèi)一個(gè) 還剩 count 0
2020-04-04 21:09:33.387111+0800 filedome[47414:1674927] 消費(fèi)一個(gè) 還剩 count 1
2020-04-04 21:09:33.387116+0800 filedome[47414:1674926] 等待 count 0
換成while 雖然能卡住但是 == 0,但是不能處理多線程同時(shí)訪問的問題。
2020-04-04 21:11:54.433800+0800 filedome[47526:1676373] 消費(fèi)一個(gè) 還剩 count 0
2020-04-04 21:11:54.498035+0800 filedome[47526:1676429] 消費(fèi)一個(gè) 還剩 count 0
2020-04-04 21:11:54.498046+0800 filedome[47526:1676385] 生產(chǎn)一個(gè) 現(xiàn)有 count 1
2020-04-04 21:11:54.498198+0800 filedome[47526:1676387] 生產(chǎn)一個(gè) 現(xiàn)有 count 2
加條件鎖 NSCondition
ticketCount == 0 的時(shí)候休眠 (-wait),ticketCount>0 的時(shí)候發(fā)信號(hào)喚醒線程進(jìn)行執(zhí)行 (-signal)
源碼
NSCondition 源碼。其中使用了一個(gè)pthread_mutex_t 的互斥鎖(同NSLock),還使用了 pthread_cond_t 的條件共同實(shí)現(xiàn)。
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 就相當(dāng)于 NSLock + Condition,通過condition 可以隨時(shí)使線程進(jìn)入休眠或被喚醒。