本章提綱:
1、NSLock
2、NSRecursiveLock
3、NSCondition
4、NSConditionLock
5、讀寫(xiě)鎖的實(shí)現(xiàn)
上一篇我們了解了synchronized的使用,這篇文章來(lái)補(bǔ)充其他我們平時(shí)開(kāi)發(fā)中常用的一些鎖的示例,以及簡(jiǎn)單的源碼探索。
1.NSLock
- NSLock的基本使用
//NSLock的創(chuàng)建
NSLock *nlock = [[NSLock alloc] init];
//NSLock加鎖
[nlock lock];
// NSLock解鎖
[nlock unlock];
它的使用非常簡(jiǎn)單,下面我們來(lái)結(jié)合具體示例。
1.1 NSLock的使用示例
- 測(cè)試代碼
多線(xiàn)程遞歸打印,未加鎖之前
未加鎖.png
可以看到打印結(jié)果,在未經(jīng)過(guò)任何臨界區(qū)處理,沒(méi)有加鎖的情況下,打印的結(jié)果非常隨意混亂。
多線(xiàn)程遞歸打印,遞歸外部加鎖NSLock

可以看到結(jié)果變得開(kāi)始有順序了,在一次線(xiàn)程操作未處理完畢之前,下一個(gè)線(xiàn)程不能訪(fǎng)問(wèn),所以每次線(xiàn)程執(zhí)行遞歸的,都是有序的打印。
多線(xiàn)程遞歸打印,遞歸內(nèi)部加鎖NSLock

可以看到,只打印了10,程序就沒(méi)有后續(xù)了,在遞歸內(nèi)部,加了一次鎖之后,
testMethod又調(diào)用了自己,然后又走到[lock lock];因?yàn)檫@段已經(jīng)加了鎖,所以要等著解鎖才能再一次執(zhí)行testMethod,因?yàn)榻怄i的代碼在testMethod之后,永遠(yuǎn)也走不到,所以這里有點(diǎn)死鎖的狀態(tài)。
通過(guò)上面三種情況的討論,可以了解到NSLock可以用于多線(xiàn)程使線(xiàn)程安全,起到保護(hù)臨界區(qū)的作用。但是不能在遞歸中使用。我們后邊要學(xué)習(xí)的NSRecursiveLock是可以在遞歸中使用的,后面詳細(xì)介紹。
1.2 NSLock源碼窺探
我們打一個(gè)符號(hào)斷點(diǎn)-[NSLock lock],看看NSLock的底層實(shí)現(xiàn)所在的框架。

可以看到NSLock是出自于Foundation框架,而oc版本的Foundation框架并沒(méi)有開(kāi)源,我們通過(guò)Swift版本的來(lái)看一下里面的具體實(shí)現(xiàn),打開(kāi)swift-corelibs-foundation-master代碼,搜索NSLock,來(lái)到它的定義的部分。

1、我們通過(guò)這個(gè)源碼可以了解到
NSLock遵循NSLocking,而NSLocking是協(xié)議。2、而NSLock的初始化
init實(shí)際上調(diào)用的是pthread_mutex_init,pthread_mutex_init是pthread框架下的一個(gè)API,所以NSLock的實(shí)際實(shí)現(xiàn)其實(shí)是對(duì)pthread的封裝。
- pthread簡(jiǎn)介
pthread是 POSIX threads 的簡(jiǎn)稱(chēng),是POSIX的線(xiàn)程標(biāo)準(zhǔn)。POSIX是可移植操作系統(tǒng)接口(Portable Operating System Interface)的簡(jiǎn)稱(chēng),其定義了操作系統(tǒng)的標(biāo)準(zhǔn)接口,旨在獲得源代碼級(jí)別的軟件可移植性。它是一套通用的多線(xiàn)程API,適用于Unix、Linux、Windows等系統(tǒng),跨平臺(tái)、可移植,使用難度大,C語(yǔ)言框架,線(xiàn)程生命周期由程序員管理。
從源碼的部分點(diǎn)進(jìn)去,可以看到一部分pthread的API,有初始化的,加鎖,解鎖的等等。

2. NSRecursiveLock
同樣NSRecursiveLock也是對(duì)pthread的封裝,具體的實(shí)現(xiàn)和NSLock的差別我們?cè)谙旅嬖创a的時(shí)候說(shuō)明。先來(lái)看它和NSLock在使用上的差別。還是上面的代碼做示例。
2.1 NSRecursiveLock的使用示例
多線(xiàn)程遞歸打印,遞歸外部加鎖NSRecursiveLock

它的效果和
NSLock是一樣的。
多線(xiàn)程遞歸打印,遞歸內(nèi)部加鎖NSRecursiveLock

可以看到,一次的遞歸調(diào)用完成了,打印出了完整的10到1。我具體的打了個(gè)斷點(diǎn),看看這個(gè)遞歸鎖在遞歸中是怎么執(zhí)行的,

左邊線(xiàn)程堆棧展示的invoke2比十個(gè)多,但是我們遞歸一共調(diào)用了十次,也就是說(shuō)別的線(xiàn)程有的調(diào)用了lock也記到了當(dāng)前線(xiàn)程里邊去,所以在解鎖的時(shí)候當(dāng)前線(xiàn)程多調(diào)用了unlock,所以引發(fā)了崩潰。
通過(guò)這個(gè)表現(xiàn)我們可以看出來(lái)NSRecursiveLock支持遞歸使用,但是不支持多線(xiàn)程!
2.2 NSRecursiveLock源碼窺探
我們來(lái)到源碼看到NSRecursiveLock的實(shí)現(xiàn)和NSLock很相似。

多了個(gè)attr的初始化和設(shè)置,傳的參數(shù)是
PTHREAD_MUTEX_RECURSIVE
pthread_mutexattr_init(attrs)
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
而且在pthread_mutex_init()調(diào)用時(shí),NSLock傳的是nil,而NSRecursiveLock把初始化好的attrs傳了進(jìn)去。
3. NSCondition
NSCondition是條件鎖,它的使用方式和信號(hào)量很相似,當(dāng)線(xiàn)程符合條件的時(shí)候才會(huì)執(zhí)行,否則會(huì)等待被阻塞,等待滿(mǎn)足條件時(shí)再執(zhí)行。
- API展示
//加鎖
[condition lock];
//與lock同時(shí)使?,解鎖
[condition unlock];
//使當(dāng)前線(xiàn)程處于等待狀態(tài)
[condition wait];
//CPU發(fā)信號(hào)告訴線(xiàn)程不?等待,繼續(xù)執(zhí)?
[condition signal];
3.1 NSCondition的使用示例
3.1.1生產(chǎn)者消費(fèi)者問(wèn)題簡(jiǎn)介
生產(chǎn)者消費(fèi)者問(wèn)題(英語(yǔ):Producer-consumer problem),也稱(chēng)有限緩沖問(wèn)題(英語(yǔ):Bounded-buffer problem),是一個(gè)多線(xiàn)程同步問(wèn)題的經(jīng)典案例。該問(wèn)題描述了兩個(gè)共享固定大小緩沖區(qū)的線(xiàn)程——即所謂的“生產(chǎn)者”和“消費(fèi)者”——在實(shí)際運(yùn)行時(shí)會(huì)發(fā)生的問(wèn)題。生產(chǎn)者的主要作用是生成一定量的數(shù)據(jù)放到緩沖區(qū)中,然后重復(fù)此過(guò)程。與此同時(shí),消費(fèi)者也在緩沖區(qū)消耗這些數(shù)據(jù)。該問(wèn)題的關(guān)鍵就是要保證生產(chǎn)者不會(huì)在緩沖區(qū)滿(mǎn)時(shí)加入數(shù)據(jù),消費(fèi)者也不會(huì)在緩沖區(qū)中空時(shí)消耗數(shù)據(jù)。
3.1.2解決這類(lèi)問(wèn)題的辦法
要解決該問(wèn)題,就必須讓生產(chǎn)者在緩沖區(qū)滿(mǎn)時(shí)休眠(要么干脆就放棄數(shù)據(jù)),等到下次消費(fèi)者消耗緩沖區(qū)中的數(shù)據(jù)的時(shí)候,生產(chǎn)者才能被喚醒,開(kāi)始往緩沖區(qū)添加數(shù)據(jù)。同樣,也可以讓消費(fèi)者在緩沖區(qū)空時(shí)進(jìn)入休眠,等到生產(chǎn)者往緩沖區(qū)添加數(shù)據(jù)之后,再喚醒消費(fèi)者。通常采用進(jìn)程間通信的方法解決該問(wèn)題,常用的方法有信號(hào)燈法等。如果解決方法不夠完善,則容易出現(xiàn)死鎖的情況。出現(xiàn)死鎖時(shí),兩個(gè)線(xiàn)程都會(huì)陷入休眠,等待對(duì)方喚醒自己。
(上述介紹摘自百度百科)
- NSCondition簡(jiǎn)單使用案例
這里我們簡(jiǎn)單實(shí)現(xiàn)了下生產(chǎn)者生產(chǎn),然后消費(fèi)者消費(fèi)的一個(gè)簡(jiǎn)單案例,只有生產(chǎn)者通知消費(fèi)者可以消費(fèi)的這么一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。
(void)testConditon{
_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 producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer];
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多線(xiàn)程影響
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal]; // 信號(hào)
[_testCondition unlock];
}
- (void)consumer{
[_testCondition lock]; // 操作的多線(xiàn)程影響
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消費(fèi)行為,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
- 消費(fèi)者來(lái)到方法
consumer,發(fā)現(xiàn)如果共享區(qū)域的資源self.ticketCount如果是0了,那么要等待,[_testCondition wait];。如果不為0,那么進(jìn)行"消費(fèi)",資源數(shù)-1。 - 然后生產(chǎn)者調(diào)用方法
producer,對(duì)共享資源self.ticketCount進(jìn)行+1的操作,然后發(fā)送signal信號(hào),通知正在等待消費(fèi)的消費(fèi)者,生產(chǎn)好了一個(gè),可以喚醒一個(gè)等待消費(fèi)的"消費(fèi)者"。 - 而上述操作公共資源
self.ticketCount的時(shí)候都需要加鎖,防止多線(xiàn)程訪(fǎng)問(wèn)。
3.2 NSCondition源碼窺探


它也是對(duì)pthread的封裝。
- init方法調(diào)用了
pthread_mutex_init和pthread_cond_init。 - lock調(diào)用了
pthread_mutex_lock,unlock調(diào)用pthread_mutex_unlock。 - wait調(diào)用
pthread_cond_wait. - signal調(diào)用的是
pthread_cond_signal - broadcast調(diào)用的是
pthread_cond_broadcast
4. NSConditionLock
NSConditionLock也是條件鎖,實(shí)際上是對(duì)NSCondition的再封裝。使用更加靈活。
- 相關(guān)API
//無(wú)條件鎖 和NSLock沒(méi)什么區(qū)別 底層都是pthread_mutex_lock 和pthread_mutex_unlock
[conditionLock lock];
[conditionLock unlock];
//有條件鎖 當(dāng)condition滿(mǎn)足條件時(shí)加鎖,不滿(mǎn)足條件不執(zhí)行
- (void)lockWhenCondition:(NSInteger)condition;
//有條件,解鎖時(shí) condition設(shè)置成相應(yīng)的條件,符合這個(gè)條件的其他等待的線(xiàn)程就能執(zhí)行了。
- (void)unlockWithCondition:(NSInteger)condition;
4.1 NSConditionLock使用示例

1、NSConditionLock的對(duì)象初始化條件為2,三個(gè)異步的線(xiàn)程,只有condition為2的條件里能執(zhí)行。
2、線(xiàn)程1執(zhí)行條件是1,所以不符合條件,不往下執(zhí)行。線(xiàn)程2滿(mǎn)足條件,可以執(zhí)行;線(xiàn)程3沒(méi)有條件限制,也可以執(zhí)行。
3、因?yàn)槎际钱惒蕉以诓l(fā)隊(duì)列中,線(xiàn)程2優(yōu)先級(jí)是DISPATCH_QUEUE_PRIORITY_LOW,線(xiàn)程3是defualt默認(rèn)優(yōu)先級(jí),而且線(xiàn)程2還睡眠了0.1,所以3大概率在2的前邊執(zhí)行。
4、而線(xiàn)程1要等著線(xiàn)程2執(zhí)行完把condition置為1才能執(zhí)行。
所以執(zhí)行的順序是3,2,1。
4.2 NSConditionLock源碼窺探分析
回到源碼,直接搜索NSConditionLock,實(shí)現(xiàn)如下。
open class NSConditionLock : NSObject, NSLocking {
//1、_cond是NSCondition()的對(duì)象,_value是Int類(lèi)型。
//方法open var condition返回的就是_value。所以這個(gè)_value實(shí)際上就是初始化condition的值
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
}
//2、lock真實(shí)調(diào)用的是 open func lock(before limit: Date) -> Bool,傳入的參數(shù)是固定的 .distantFuture
open func lock() {
let _ = lock(before: Date.distantFuture)
}
//3、解鎖時(shí) 調(diào)用的是broadcast也就是pthread_cond_broadcast,和unlock。
open func unlock() {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_cond.broadcast()
_cond.unlock()
}
//返回的是value
open var condition: Int {
return _value
}
//4、lock(whenCondition condition: Int) 底層實(shí)際調(diào)用的是lock(whenCondition condition: Int, before limit: Date) -> Bool
//limit傳入的是固定的.distantFuture
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)
}
//5、unlock(withCondition condition: Int)調(diào)用的是broadcast和unlock
open func unlock(withCondition condition: Int) {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_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
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
//線(xiàn)程不為空 或者 傳進(jìn)來(lái)的 _value和傳進(jìn)來(lái)的condition不相同進(jìn)入循環(huán)
while _thread != nil || _value != condition {
//解鎖條件 具體看_cond.wait的實(shí)現(xiàn),
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
open var name: String?
}
1、_cond是NSCondition()的對(duì)象,_value是Int類(lèi)型。所以這個(gè)_value實(shí)際上就是初始化condition的值。
2、lock真實(shí)調(diào)用的是 open func lock(before limit: Date) -> Bool,傳入的參數(shù)是固定的 .distantFuture。
3、解鎖時(shí) 調(diào)用的是broadcast也就是pthread_cond_broadcast,和unlock。
4、lock(whenCondition condition: Int) 底層實(shí)際調(diào)用的是lock(whenCondition condition: Int, before limit: Date) -> Bool,limit傳入的是固定的.distantFuture。
5、unlock(withCondition condition: Int)調(diào)用的是broadcast和unlock
- wait(until limit: Date) -> Bool
open func wait(until limit: Date) -> Bool {
#if os(Windows)
return SleepConditionVariableSRW(cond, mutex, timeoutFrom(date: limit), 0)
#else
//timeOut不成立 就走else 也就是timeout為nil(timeout為nil時(shí)是date傳的值不大于0) 就走else 否則跳過(guò)語(yǔ)句
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
//接著等待
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
#endif
}
- timeSpecFrom
public static let distantFuture = Date(timeIntervalSinceReferenceDate: 63113904000.0)
public var timeIntervalSinceNow: TimeInterval {
return self.timeIntervalSinceReferenceDate - CFAbsoluteTimeGetCurrent()
}
private func timeSpecFrom(date: Date) -> timespec? {
//date.timeIntervalSinceNow 不大于0 返回nil ,大于0 跳過(guò)這個(gè)語(yǔ)句。
guard date.timeIntervalSinceNow > 0 else {
return nil
}
let nsecPerSec: Int64 = 1_000_000_000
let interval = date.timeIntervalSince1970
let intervalNS = Int64(interval * Double(nsecPerSec))
return timespec(tv_sec: Int(intervalNS / nsecPerSec),
tv_nsec: Int(intervalNS % nsecPerSec))
}
結(jié)合上述代碼,可以了解到wait方法,當(dāng)傳進(jìn)來(lái)的limit再次是distantFuture時(shí),timeSpecFrom才走guard語(yǔ)句中的返回,才不繼續(xù)等待。傳進(jìn)來(lái)的不是distantFuture返回的是當(dāng)前的時(shí)間,也就是大于0的值,是ture
distantFuture 從代碼定義看,是一個(gè)非常大的值。
(而這個(gè)終止等待的條件,有可能是在broadcast廣播的時(shí)候,或者在unlock觸發(fā)跳出while的條件,后面嘗試驗(yàn)證一下)。
5.讀寫(xiě)鎖的實(shí)現(xiàn)
- 讀寫(xiě)鎖簡(jiǎn)介
讀寫(xiě)鎖,又叫共享-獨(dú)占鎖。從命名上來(lái)看,讀寫(xiě)鎖擁有兩把鎖,讀鎖和寫(xiě)鎖。它的特點(diǎn)是: - 同一時(shí)間只允許一個(gè)線(xiàn)程對(duì)共享資源進(jìn)行寫(xiě)操作;
- 當(dāng)進(jìn)行寫(xiě)操作的時(shí)候,同一時(shí)間其他線(xiàn)程都會(huì)被阻塞;
- 當(dāng)進(jìn)行讀操作的時(shí)候,同一時(shí)間所有的寫(xiě)操作都會(huì)被阻塞;
- 當(dāng)進(jìn)行讀操作的時(shí)候,同一時(shí)間其他線(xiàn)程可以進(jìn)行讀操作共享資源;
具體實(shí)現(xiàn)
根據(jù)上述要求,我們可以總結(jié)出來(lái),就是寫(xiě)的時(shí)候,在寫(xiě)之前的所有操作要完畢,只能進(jìn)行一個(gè)寫(xiě)操作。這符合GCD柵欄函數(shù)的特性,柵欄里的內(nèi)容執(zhí)行之前,會(huì)阻塞后續(xù)的同一隊(duì)列上的任務(wù),柵欄之前的操作執(zhí)行完,才會(huì)執(zhí)行柵欄里的操作。所以寫(xiě)放到柵欄里,然后所有的任務(wù)并發(fā)就ok了。
- (void)testReaderAndWriter{
self.testQueue = dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 50; i++) {
[self writer];
[self reader];
[self reader];
[self writer];
[self reader];
}
}
//讀者
- (void)reader{
dispatch_async(self.testQueue, ^{
NSLog(@"讀取剩余票數(shù)%lu",(unsigned long)self.ticketCount);
});
}
//寫(xiě)操作
- (void)writer{
//進(jìn)行寫(xiě)操作
dispatch_barrier_sync(self.testQueue, ^{
self.ticketCount++;
});
}
實(shí)現(xiàn)結(jié)果

