iOS-鎖-NSCondition&NSConditionLock

NSCondition

條件鎖,顧名思義,就是滿足某些條件才會開鎖。NSCondition,可以確保線程僅在滿足特定條件時才能獲取鎖。一旦獲得了鎖并執(zhí)行了代碼的關(guān)鍵部分,線程就可以放棄該鎖并將關(guān)聯(lián)條件設(shè)置為新的條件。條件本身是任意的:可以根據(jù)應(yīng)用程序的需要定義它們。

NSCondition對象實際上作為一個鎖和一個線程檢查器:鎖主要為了當(dāng)檢測條件時保護(hù)數(shù)據(jù)源,執(zhí)行條件引發(fā)的任務(wù);線程檢查器主要是根據(jù)條件決定是否繼續(xù)運行線程,即線程是否被阻塞。通俗的說,也就是條件成立,才會執(zhí)行鎖住的代碼。條件不成立時,線程就會阻塞,直到另一個線程向條件對象發(fā)出信號解鎖為止。

下面我們看一個例子:

- (void)conditionTest {
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self addTickets];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self addTickets];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self minusTickets];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self minusTickets];
        });
    }
}

- (void)addTickets {
    self.ticketCount += 1;
    NSLog(@"加一個現(xiàn)有ticketCount==%zd",self.ticketCount);
}

- (void)minusTickets {
    while (self.ticketCount == 0) {
        NSLog(@"==沒有ticketCount==");
        return;
    }
    
    self.ticketCount -= 1;
    NSLog(@"減一個剩下ticketCount==%zd",self.ticketCount);
}

運行程序,我們發(fā)現(xiàn)控制臺的輸出是有問題的:

image

此時,我們就可以使用條件鎖解決問題。我們只需要對程序作如下改動就可以正常執(zhí)行:

- (void)addTickets {
    [self.condition lock];
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"現(xiàn)有ticketCount==%zd",self.ticketCount);
    [self.condition unlock];
    [self.condition signal];
}

- (void)minusTickets {
    [self.condition lock];
    while (self.ticketCount == 0) {
        NSLog(@"==沒有ticketCount==");
        [self.condition wait];
        return;
    }
    
    self.ticketCount -= 1;
    NSLog(@"減一個,剩下ticketCount==%zd",self.ticketCount);
    [self.condition unlock];
}

那么NSCondition到底做了什么呢?我們來看看源碼,然而,Objective-C代碼并不能看到NSCondition的具體實現(xiàn):

@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

需要使用swift源碼進(jìn)行查看:

open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)

    public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
    
    deinit {
        pthread_mutex_destroy(mutex)
        pthread_cond_destroy(cond)
        mutex.deinitialize(count: 1)
        cond.deinitialize(count: 1)
        mutex.deallocate()
        cond.deallocate()
    }
    
    // 一般用于多線程同時訪問、修改同一個數(shù)據(jù)源,保證在同一 時間內(nèi)數(shù)據(jù)源只被訪問、修改一次,
    // 其他線程的命令需要在lock 外等待,只到 unlock ,才可訪問
    open func lock() {
        pthread_mutex_lock(mutex)
    }
    
    // 釋放鎖,與lock成對出現(xiàn)
    open func unlock() {
        pthread_mutex_unlock(mutex)
    }
    
    // 讓當(dāng)前線程處于等待狀態(tài),阻塞
    open func wait() {
        pthread_cond_wait(cond, mutex)
    }

    // 讓當(dāng)前線程等待到某個時間,阻塞
    open func wait(until limit: Date) -> Bool {
        guard var timeout = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_cond_timedwait(cond, mutex, &timeout) == 0
    }
    
    // 發(fā)信號告訴線程可以繼續(xù)執(zhí)行,喚醒線程
    open func signal() {
        pthread_cond_signal(cond)
    }
    
    open func broadcast() {
        pthread_cond_broadcast(cond) // wait  signal
    }
    
    open var name: String?
}

可以看到,該對象還是對pthread_mutex的一層封裝,NSCondition也是一種互斥鎖。當(dāng)我們需要等待某個條件的時候,也就是條件不滿足的時候,就可以使用wait方法來阻塞線程,當(dāng)條件滿足了,使用signal方法發(fā)送信號喚醒線程。

NSConditionLock

說到NSCondition,就不得不說一下NSConditionLock,NSConditionLockNSCondition又做了一層封裝,自帶條件探測,能夠更簡單靈活的使用。

我們來看看NSConditionLock的相關(guān)源碼:

Objective-C下,只能看到方法的定義,并不能看到實現(xiàn):

@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

我們使用swift查看一下:

internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
    
public convenience override init() {
    self.init(condition: 0)
}
    
public init(condition: Int) {
    _value = condition
}

// 表示 xxx 期待獲得鎖,
// 如果沒有其他線程獲得鎖(不需要判斷內(nèi)部的 condition) 那它能執(zhí)行此行以下代碼,
// 如果已經(jīng)有其他線程獲得鎖(可能是條件鎖,或者無條件 鎖),則等待,直至其他線程解鎖
open func lock() {
    let _ = lock(before: Date.distantFuture)
}

open func unlock() {
    _cond.lock()
    _thread = nil
    _cond.broadcast()
    _cond.unlock()
}
    
open var condition: Int {
    return _value
}

// 表示如果沒有其他線程獲得該鎖,但是該鎖內(nèi)部的 condition不等于A條件,它依然不能獲得鎖,仍然等待。
// 如果內(nèi)部的condition等于A條件,并且沒有其他線程獲得該鎖,則執(zhí)行任務(wù),同時設(shè)置它獲得該鎖
// 其他任何線程都將等待它代碼的完成,直至它解鎖。
open func lock(whenCondition condition: Int) {
    let _ = lock(whenCondition: condition, before: Date.distantFuture)
}

open func `try`() -> Bool {
    return lock(before: Date.distantPast)
}
    
open func tryLock(whenCondition condition: Int) -> Bool {
    return lock(whenCondition: condition, before: Date.distantPast)
}

// 表示釋放鎖,同時把內(nèi)部的condition設(shè)置為A條件
open func unlock(withCondition condition: Int) {
    _cond.lock()
    _thread = nil
    _value = condition
    _cond.broadcast()
    _cond.unlock()
}

open func lock(before limit: Date) -> Bool {
    _cond.lock()
    while _thread != nil {
        if !_cond.wait(until: limit) {
            _cond.unlock()
            return false
        }
    }
    _thread = pthread_self()
    _cond.unlock()
    return true
}
    
// 表示如果被鎖定(沒獲得 鎖),并超過該時間則不再阻塞線程。
// 需要注意的是:返回的值是NO,它沒有改變鎖的狀態(tài),這個函 數(shù)的目的在于可以實現(xiàn)兩種狀態(tài)下的處理
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
}
    
open var name: String?

可以看出,觸發(fā)的喚醒線程的條件是傳入的condition取值,和我們創(chuàng)建鎖的時候值要相同,我們可以在釋放當(dāng)前線程鎖的時候重新設(shè)置其他線程傳入的condition值,這樣也就達(dá)到了喚醒其他線程的目的。如果創(chuàng)建鎖的值和傳入的值都不能匹配,則會進(jìn)入阻塞狀態(tài)。

下面我們來看個例子:

- (void)conditionLockTest {
    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];
    });
}

線程1調(diào)用[NSConditionLock lockWhenCondition:],此時此刻因為不滿足當(dāng)前條件,所以會進(jìn)入等待狀態(tài)。此時當(dāng)前的線程3調(diào)用[NSConditionLock lock:],本質(zhì)上是調(diào)用 [NSConditionLock lockBeforeDate:],這里不需要比對條件值,所以線程 3會打印。接下來線程2執(zhí)行[NSConditionLock lockWhenCondition:],因為滿足條件值,所以線程2會打印,打印完成后會調(diào)用[NSConditionLock unlockWithCondition:],這個時候?qū)l件設(shè)置為 1,并發(fā)送boradcast, 此時線程1接收到當(dāng)前的信號,喚醒執(zhí)行并打印。
自此當(dāng)前打印為 線程 3->線程 2 -> 線程 1。
[NSConditionLock lockWhenCondition:]:這里會根據(jù)傳入的condition 值和value值進(jìn)行對比,如果不相等,這里就會阻塞。而[NSConditionLock unlockWithCondition:]會先更改當(dāng)前的value值,然后調(diào)用boradcast,喚醒當(dāng)前的線程。

總結(jié)

相同點:

  • 都是互斥鎖
  • 通過條件變量來控制加鎖、釋放鎖,從而達(dá)到阻塞線程、喚醒線程的目的

不同點:

  • NSCondition是基于對pthread_mutex的封裝,而NSConditionLock是對NSCondition做了一層封裝
  • NSCondition是需要手動讓線程進(jìn)入等待狀態(tài)阻塞線程、釋放信號喚醒線程,NSConditionLock則只需要外部傳入一個值,就會依據(jù)這個值進(jìn)行自動判斷是阻塞線程還是喚醒線程
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 對于iOS中各種鎖的學(xué)習(xí)總結(jié),供日后查閱 引子 日常開發(fā)中,@property (nonatomic, st...
    Tr2e閱讀 983評論 1 1
  • 在平時的開發(fā)中經(jīng)常使用到多線程,在使用多線程的過程中,難免會遇到資源競爭的問題,那我們怎么來避免出現(xiàn)這種問題那? ...
    IAMCJ閱讀 3,329評論 2 25
  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,627評論 0 6
  • 目錄:1.為什么要線程安全2.多線程安全隱患分析3.多線程安全隱患的解決方案4.鎖的分類-13種鎖4.1.1OSS...
    二斤寂寞閱讀 1,243評論 0 3
  • 轉(zhuǎn)載:談 iOS 的鎖 又到了春天挪坑的季節(jié),想起多次被問及到鎖的概念,決定好好總結(jié)一番。 翻看目前關(guān)于 iOS ...
    小鯤鵬閱讀 588評論 0 0

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