條件鎖 NSConditionLock
1、定義
條件鎖就是有特定條件的鎖,所謂條件只是一個抽象概念,由程序猿自定義。說白了就是「有條件的互斥鎖」.對于NSConditionLock,官方文檔的描述是這樣的:
使用NSConditionLock對象,可以確保線程僅在滿足特定條件時才能獲取鎖。 一旦獲得了鎖并執(zhí)行了代碼的關(guān)鍵部分,線程就可以放棄該鎖并將關(guān)聯(lián)條件設(shè)置為新的條件。 條件本身是任意的:您可以根據(jù)應(yīng)用程序的需要定義它們。
NSConditionLock 同樣實現(xiàn)了 NSLocking 協(xié)議,并定義了一些特定的接口:
@interface NSConditionLock : NSObject <NSLocking> {
@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,沒有其他任何限制,使用起來就像UIView的tag一樣。類的定義中,不帶condition 的和 NSLock 沒什么區(qū)別。特有的:
只讀屬性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誰先執(zhí)行不一定,但是線程1一定是在線程2之后執(zhí)行的。
執(zhí)行線程1、2、3(順序不確定的)
但線程1 、2獲取鎖需要看 condition 是否滿足
線程3 獲取鎖不需要條件,先獲取到鎖,開始執(zhí)行任務(wù)(不一定是最先執(zhí)行的)
線程2 因為條件是2,所以線程2可以執(zhí)行。當線程2釋放鎖時將condition置為1,滿足線程1的條件,此時線程1獲取到鎖執(zhí)行任務(wù)
線程1 釋放鎖時將condition置為0,不再執(zhí)行任務(wù)
在 [conditionLock lockWhenCondition:1]; 打斷點。匯編查看
然后下符號斷點 lockWhenCondition: + -> Symbolic BreakPoint... -> -[NSConditionLock lockWhenCondition:]
然后下符號斷點 unlockWhenCondition: + -> Symbolic BreakPoint... -> -[NSConditionLock unlockWhenCondition:]
這個date 哪來的干什么的不知道 看一下文檔。 xcode中選中 lockWhenCondition -> Help -> Search Documentation for Selected Text
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
被鎖的話此方法將阻塞線程的執(zhí)行,直到解鎖或超時為止。
通過調(diào)試,發(fā)現(xiàn)NSConditionLock 是對 NSCondition 的封裝。通過加鎖來解決線程完全問題,那么條件是怎么什么地方處理的呢?
最后發(fā)現(xiàn)有調(diào)用了 waitUntilDate: 這個跟 [_testCondition wait]; 是一樣的,條件不一樣的時候進行等待。
執(zhí)行完調(diào)用 broadcast 進行廣播
即使在未來某個時間點可以滿足條件,但-tryLockWhenCondition:只是根據(jù)當前condition獲取鎖
無論能否獲取到鎖,該線程都會繼續(xù)向下執(zhí)行,不會阻塞
3、應(yīng)用
//主線程中
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的代碼后執(zhí)行
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的代碼后執(zhí)行
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的代碼后執(zhí)行
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 參數(shù)為 0,而加鎖條件是 condition 為 1,所以加鎖失敗。locakWhenCondition 與 lock 方法類似,加鎖失敗會阻塞線程,所以線程 1 會被阻塞著,而 tryLockWhenCondition 方法就算條件不滿足,也會返回 NO,不會阻塞當前線程。
回到上面的代碼,線程 2 執(zhí)行了 [lock unlockWithCondition:2]; 所以 Condition 被修改成了 2。
而線程 3 的加鎖條件是 Condition 為 2, 所以線程 3 才能加鎖成功,線程 3 執(zhí)行了 [lock unlock]; 解鎖成功且不改變 Condition 值。
線程 4 的條件也是 2,所以也加鎖成功,解鎖時將 Condition 改成 1。這個時候線程 1 終于可以加鎖成功,解除了阻塞。
從上面可以得出,NSConditionLock 還可以實現(xiàn)任務(wù)之間的依賴。
4、注意事項
由于NSConditionLock也實現(xiàn)了NSLocking協(xié)議,可以通過-lock加鎖以及-unlock解鎖,但無論當前condition是多少,-lock都可以獲得鎖,-unlock也不會改變當前condition的值。
由于是同一把鎖,同一時間只能由一個線程持有,如果當前condition滿足線程1的持鎖條件,但線程2通過-lock獲取沒有條件的鎖,此時他們誰先搶到鎖就執(zhí)行誰的任務(wù),另一個就要等鎖釋放。
通過-lockWhenCondition:獲取鎖時,線程會休眠等待,必須有其他線程設(shè)置為合適的條件,否則該線程永遠獲取不到鎖
熟悉了條件鎖NSConditionLock的使用方法,可以用一個任務(wù)編號(條件)來指定不同線程任務(wù)的執(zhí)行順序。
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:都會走這個函數(shù):
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()喚醒所有等待在這個鎖的線程