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)控制臺的輸出是有問題的:
此時,我們就可以使用條件鎖解決問題。我們只需要對程序作如下改動就可以正常執(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,NSConditionLock對NSCondition又做了一層封裝,自帶條件探測,能夠更簡單靈活的使用。
我們來看看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)行自動判斷是阻塞線程還是喚醒線程