iOS底層分析 - 線程鎖(四)條件鎖 NSconditionLock

條件鎖 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()喚醒所有等待在這個鎖的線程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容