在編程中,引入了對象互斥鎖的概念截珍,來保證共享數(shù)據(jù)操作的完整性。每個對象都對應(yīng)于一個可稱為" 互斥鎖" 的標記箩朴,這個標記用來保證在任一時刻岗喉,只能有一個線程訪問該對象。對于互斥鎖炸庞,如果資源已經(jīng)被占用钱床,資源申請者只能進入睡眠狀態(tài)。
互斥鎖又分為遞歸鎖和非遞歸鎖埠居。
- 遞歸鎖是一種可以多次被同一線程持有的鎖查牌。
- 非遞歸鎖是只能一個線程鎖定一次,想要再次鎖定滥壕,就必須先要解鎖纸颜,否則就會發(fā)生死鎖現(xiàn)象。
非遞歸鎖
pthread_mutex
創(chuàng)建和銷毀
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//靜態(tài)初始化互斥量
int pthread_mutex_init(pthread_mutex_t*mutex,pthread_mutexattr_t*attr);//動態(tài)初始化互斥量
int pthread_mutex_destory(pthread_mutex_t*mutex);//注銷互斥量
加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t*mutex);
- pthread_mutex_lock`給互斥量加鎖绎橘。當另一個線程來獲取這個鎖的時候胁孙,發(fā)現(xiàn)這個鎖已經(jīng)加鎖,那么這個線程就會進入休眠狀態(tài)称鳞,直到這個互斥量被解鎖涮较,線程才會重新被喚醒。
-
pthread_mutex_trylock
當互斥量已經(jīng)被鎖住時調(diào)用該函數(shù)將返回錯誤代碼EBUSY
冈止,如果當前互斥量沒有被鎖狂票,則會執(zhí)行和pthread_mutex_lock
一樣的效果。 -
pthread_mutex_unlock
對互斥量進行解鎖熙暴。
NSLock
我們在swift
版本開源的CoreFoundation
框架下我們可以看到關(guān)于NSLock
的完整定義闺属。
注意:以下代碼只摘取了關(guān)鍵部分慌盯,其他兼容性代碼已被省略。
創(chuàng)建
public override init() {
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
銷毀
deinit {
pthread_mutex_destroy(mutex)
}
加鎖
open func lock() {
pthread_mutex_lock(mutex)
}
解鎖
open func unlock() {
pthread_mutex_unlock(mutex)
}
lock(before limit: Date)
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with:
}
這個函數(shù)的主要作用就是在某一個時間點之前不斷的嘗試加鎖屋剑,主要做了下面幾件事情润匙。
-
pthread_mutex_trylock
嘗試加鎖,如果加鎖成功則直接返回true
唉匾。 -
timedLock
通過一層while
循環(huán)調(diào)用pthread_mutex_trylock
不斷嘗試加鎖孕讳。如果失敗失敗則返回false
,如果成功則返回true
巍膘。
通過上述代碼我們可以發(fā)現(xiàn)NSLock
底層呢厂财,其實也就是基于pthread_mutex
進行了一次面向?qū)ο蟮姆庋b。因為他是比pthread_mutex
更高一級的API峡懈,所以在性能方面呢比pthread_mutex
稍微差一點璃饱。
NSCondition
NSCondition
和NSLock
一樣都是基于pthread_mutex
進行面向?qū)ο蠓盅b的一個非遞歸的互斥鎖,在swift
版本開源的CoreFoundation
框架下我們同樣可以找到相關(guān)的開源代碼肪康。
條件對象既充當給定線程中的鎖又充當檢查點荚恶。鎖在測試條件并執(zhí)行條件觸發(fā)的任務(wù)時保護您的代碼。檢查點行為要求條件在線程繼續(xù)執(zhí)行其任務(wù)之前為真磷支。條件不成立時谒撼,線程將阻塞。它保持阻塞狀態(tài)雾狈,直到另一個線程向條件對象發(fā)出信號為止廓潜。
注意:以下代碼只摘取了關(guān)鍵部分,其他兼容性代碼已被省略善榛。
創(chuàng)建
public override init() {
pthread_cond_init(cond, nil)//初始化條件變量
pthread_mutex_init(mutex, nil)//初始化互斥鎖
}
銷毀
deinit {
pthread_mutex_destroy(mutex)
}
加鎖
open func lock() {
pthread_mutex_lock(mutex)
}
解鎖
open func unlock() {
pthread_mutex_unlock(mutex)
}
它在NSLock
的基礎(chǔ)上又增加了些新的功能辩蛋。
等待
open func wait() {
pthread_cond_wait(cond, mutex)
}
阻塞當前線程,直到接收到條件信號為止移盆。
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
阻塞當前線程悼院,直到接收到條件信號或達到指定的時間限制為止。
激活
signal
open func signal() {
pthread_cond_signal(cond)
}
我們通過查閱pthread
開源代碼如下
/*
* Signal a condition variable, waking only one thread.
*/
PTHREAD_NOEXPORT_VARIANT
int
pthread_cond_signal(pthread_cond_t *ocond)
{
return _pthread_cond_signal(ocond, false, MACH_PORT_NULL);
}
發(fā)信號通知條件變量咒循,僅喚醒一個線程樱蛤。
broadcast
open func broadcast() {
pthread_cond_broadcast(cond) // wait signal
}
我們在pthread
的開源代碼中找到這么代碼。
/*
* Signal a condition variable, waking up all threads waiting for it.
*/
PTHREAD_NOEXPORT_VARIANT
int
pthread_cond_broadcast(pthread_cond_t *ocond)
{
return _pthread_cond_signal(ocond, true, MACH_PORT_NULL);
}
蘋果的注釋告訴我們這是一個(發(fā)信號通知條件變量剑鞍,喚醒所有等待它的線程昨凡。)的函數(shù)。
NSConditionLock
使用NSConditionLock
對象蚁署,可以確保線程僅在滿足特定條件時才能獲取鎖便脊。 一旦獲得了鎖并執(zhí)行了臨界區(qū)的代碼,線程就可以放棄該鎖并將關(guān)聯(lián)條件設(shè)置為新的條件光戈。 條件本身是任意的:您可以根據(jù)應(yīng)用程序的需要定義它們哪痰。
NSConditionLock
是NSCondition
的升級版遂赠,其內(nèi)部持有了一個條件鎖NSCondition
。
internal var _cond = NSCondition()//內(nèi)存持有的條件鎖
internal var _value: Int//條件變量
internal var _thread: _swift_CFThreadRef?//當前的線程
加鎖
open func lock() {
let _ = lock(before: Date.distantFuture)
}
其內(nèi)部是調(diào)用lock(before limit: Date) -> Bool
來完成的晌杰。
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
}
這個函數(shù)做了以下幾件事情跷睦。
- 首先調(diào)用條件鎖加鎖,保證接下來代碼的線程安全性肋演。然后判斷當前是否有線程正在運行抑诸。
- 如果有線程正在運行,然后調(diào)用條件鎖阻塞當前線程爹殊。如果條件不成立蜕乡,獲取當前正在運行的線程賦值
_thread
屬性,然后對條件鎖進行一個unlock
解鎖的操作梗夸。 - 這里主要是保障條件變量的線程安全性层玲。
解鎖
open func unlock() {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_cond.broadcast()
_cond.unlock()
}
首先調(diào)用
NSCondition
的lock
方法給接下來的臨界區(qū)加鎖。對
_thread
屬性置為nil
反症。發(fā)信號通知條件變量辛块,喚醒所有等待它的線程。
調(diào)用
NSCondition
的unlock
方法解鎖铅碍。
lockWhenCondition:
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
其內(nèi)部調(diào)用了lock(whenCondition condition: Int, before limit: Date) -> Bool
lockWhenCondition: beforeDate:
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
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
這里和上面的lock
實現(xiàn)原理差不多憨降,只是多了一個_value != condition
條件的判斷。
unlockWithCondition:
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()
}
這里的實現(xiàn)邏輯和unlock
的相同该酗,只是多了一步修改條件變量的步驟_value = condition
。
tryLock
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
和lockWhenCondition
一樣內(nèi)部都是通過調(diào)用lock(whenCondition condition: Int, before limit: Date) -> Bool
實現(xiàn)的士嚎。
遞歸鎖
NSRecursiveLock
我們有幸得到了NSRecursiveLock
底層源碼呜魄,我們一起來看看它是如何實現(xiàn)遞歸的。
注意:以下代碼只截取了關(guān)鍵部分莱衩。
創(chuàng)建
public override init() {
super.init()
withUnsafeMutablePointer(to: &attrib) { attrs in
pthread_mutexattr_init(attrs)
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(mutex, attrs)
}
}
-
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
這是一句關(guān)鍵性的代碼爵嗅,通過這個type
來區(qū)分是否是遞歸鎖。
#define PTHREAD_MUTEX_NORMAL 0 //普通非遞歸鎖
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //遞歸鎖
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL //默認是普通非遞歸鎖
加鎖
open func lock() {
pthread_mutex_lock(mutex)
}
底層是調(diào)用了pthread
加鎖函數(shù)笨蚁。
解鎖
open func unlock() {
pthread_mutex_unlock(mutex)
}
銷毀
deinit {
pthread_mutex_destroy(mutex)
}
總結(jié)
NSRecursiveLock
底層就是對pthread_mutex
的封裝睹晒,通過設(shè)置標識來區(qū)分是否為遞歸鎖。
遞歸鎖的實現(xiàn)原理
在pthread
我們通過搜索PTHREAD_MUTEX_RECURSIVE
遞歸類型可找到如下代碼括细。
PTHREAD_ALWAYS_INLINE
static inline bool
_pthread_mutex_is_recursive(_pthread_mutex *mutex)
{
return (mutex->mtxopts.options.type == PTHREAD_MUTEX_RECURSIVE);
}
通過類型匹配伪很,返回當前的鎖是否是遞歸鎖。
遞歸加鎖的流程
我們在通過從pthread_mutex_lock
以此查看源碼奋单,看是否能找到調(diào)用_pthread_mutex_is_recursive
的地方锉试。
pthread_mutex_lock->_pthread_mutex_firstfit_lock->_pthread_mutex_firstfit_lock_slow->_pthread_mutex_lock_handle_options
在_pthread_mutex_lock_handle_options中,找到了這個函數(shù)览濒。
static int
_pthread_mutex_lock_handle_options(_pthread_mutex *mutex, bool trylock,
uint64_t *tidaddr)
{
if (mutex->mtxopts.options.type == PTHREAD_MUTEX_NORMAL) {
// NORMAL does not do EDEADLK checking
return 0;
}
uint64_t selfid = _pthread_selfid_direct();
if (os_atomic_load(tidaddr, relaxed) == selfid) {
if (_pthread_mutex_is_recursive(mutex)) {
if (mutex->mtxopts.options.lock_count < USHRT_MAX) {
mutex->mtxopts.options.lock_count += 1;
return mutex->mtxopts.options.lock_count;
} else {
return -EAGAIN;
}
} else if (trylock) { /* PTHREAD_MUTEX_ERRORCHECK */
// <rdar://problem/16261552> as per OpenGroup, trylock cannot
// return EDEADLK on a deadlock, it should return EBUSY.
return -EBUSY;
} else { /* PTHREAD_MUTEX_ERRORCHECK */
return -EDEADLK;
}
}
// Not recursive, or recursive but first lock.
return 0;
}
在這里我們發(fā)現(xiàn)如果是遞歸鎖呆盖,那么這把鎖維護了一個lock_count
的引用計數(shù)拖云。每加一次鎖都會對引用計數(shù)執(zhí)行+1的操作。
這里截取主要邏輯
int
_pthread_mutex_firstfit_lock_slow(_pthread_mutex *mutex, bool trylock)
{
res = _pthread_mutex_lock_handle_options(mutex, trylock, tidaddr);
if (res > 0) {
recursive = 1;
res = 0;
goto out;
} else if (res < 0) {
res = -res;
goto out;
}
}
接下來會跳轉(zhuǎn)到out
代碼塊中应又。
out:
#if PLOCKSTAT
if (res == 0) {
PLOCKSTAT_MUTEX_ACQUIRE((pthread_mutex_t *)mutex, recursive, 0);
} else {
PLOCKSTAT_MUTEX_ERROR((pthread_mutex_t *)mutex, res);
}
#endif
return res;
}
我們發(fā)現(xiàn)當res
為0宙项,也就是確定是遞歸鎖的時候,會對當前的鎖進行一次持有株扛∮瓤穑可以理解為對鎖的引用計數(shù)加1。
遞歸解鎖的流程
pthread_mutex_unlock->_pthread_mutex_firstfit_unlock_slow->_pthread_mutex_firstfit_unlock_updatebits->_pthread_mutex_unlock_handle_options
同理席里,我們發(fā)現(xiàn)當我們在調(diào)用解鎖的函數(shù)時lock_count
會做一次-1的操作叔磷。再判斷是遞歸鎖和lock_countda
大于0時返回1。
static int
_pthread_mutex_unlock_handle_options(_pthread_mutex *mutex, uint64_t *tidaddr)
{
if (mutex->mtxopts.options.type == PTHREAD_MUTEX_NORMAL) {
// NORMAL does not do EDEADLK checking
return 0;
}
uint64_t selfid = _pthread_selfid_direct();
if (os_atomic_load(tidaddr, relaxed) != selfid) {
return -EPERM;
} else if (_pthread_mutex_is_recursive(mutex) &&
--mutex->mtxopts.options.lock_count) {
return 1;
}
return 0;
}
主要的解鎖邏輯
static inline int
_pthread_mutex_firstfit_unlock_updatebits(_pthread_mutex *mutex,
uint32_t *flagsp, uint32_t **mutexp, uint32_t *lvalp, uint32_t *uvalp)
{
int res = _pthread_mutex_unlock_handle_options(mutex, tidaddr);
if (res > 0) {
// Valid recursive unlock
if (flagsp) {
*flagsp = flags;
}
PLOCKSTAT_MUTEX_RELEASE((pthread_mutex_t *)mutex, 1);
return 0;
} else if (res < 0) {
PLOCKSTAT_MUTEX_ERROR((pthread_mutex_t *)mutex, -res);
return -res;
}
return 0;
}
當_pthread_mutex_unlock_handle_options
返回的結(jié)果大于0的時候進行正常的遞歸解鎖操作奖磁「幕可以理解為鎖的引用計數(shù)加1。
總結(jié)
遞歸鎖我們可以簡單理解為是一個OC對象咖为,它擁有一個引用計數(shù)的屬性秕狰,當我們進行多層次的遞歸加鎖時,引用計數(shù)會加一躁染,每釋放一層鎖的時候鸣哀,引用計數(shù)會加一,當引用計數(shù)為0的時候吞彤,這個遞歸鎖就會被釋放我衬。等待下一個線程對他的持有。
總結(jié)
這里介紹了基于pthread_mutex
實現(xiàn)的遞歸鎖以及非遞歸鎖饰恕。
非遞歸鎖里面
NSLock
是基于pthread_mutex
實現(xiàn)的挠羔。條件鎖
NSCondition
也是基于pthread_mutex
實現(xiàn)的,基礎(chǔ)功能和NSLock
相似埋嵌。在NSLock
的基礎(chǔ)上增加了一些功能破加。條件鎖
NSConditionLock
是基于NSCondition
實現(xiàn)的,在其基礎(chǔ)上進一步的增加了一下其他功能雹嗦。遞歸鎖
NSRecursiveLock
也是基于pthread_mutex
實現(xiàn)的范舀。運用了引用計數(shù)的思想。通過維護引用計數(shù)來達到一個加解鎖平衡的狀態(tài)了罪。以上代碼的分析來自swift版本的
CoreFoundation
和pthread
锭环。均可在蘋果的開源代碼庫下載到。