iOS 原理探究-互斥鎖

在編程中,引入了對象互斥鎖的概念截珍,來保證共享數(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

NSConditionNSLock一樣都是基于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)用程序的需要定義它們哪痰。

NSConditionLockNSCondition的升級版遂赠,其內(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)用NSConditionlock方法給接下來的臨界區(qū)加鎖。

  • _thread屬性置為nil反症。

  • 發(fā)信號通知條件變量辛块,喚醒所有等待它的線程。

  • 調(diào)用NSConditionunlock方法解鎖铅碍。

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版本的CoreFoundationpthread锭环。均可在蘋果的開源代碼庫下載到。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泊藕,一起剝皮案震驚了整個濱河市田藐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖汽久,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹤竭,死亡現(xiàn)場離奇詭異,居然都是意外死亡景醇,警方通過查閱死者的電腦和手機臀稚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來三痰,“玉大人吧寺,你說我怎么就攤上這事∩⒔伲” “怎么了稚机?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長获搏。 經(jīng)常有香客問我赖条,道長,這世上最難降的妖魔是什么常熙? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任纬乍,我火速辦了婚禮,結(jié)果婚禮上裸卫,老公的妹妹穿的比我還像新娘仿贬。我一直安慰自己,他們只是感情好墓贿,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布茧泪。 她就那樣靜靜地躺著,像睡著了一般聋袋。 火紅的嫁衣襯著肌膚如雪队伟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天舱馅,我揣著相機與錄音,去河邊找鬼刀荒。 笑死代嗤,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的缠借。 我是一名探鬼主播干毅,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泼返!你這毒婦竟也來了硝逢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎渠鸽,沒想到半個月后叫乌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡徽缚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年憨奸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凿试。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡排宰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出那婉,到底是詐尸還是另有隱情板甘,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布详炬,位于F島的核電站盐类,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏痕寓。R本人自食惡果不足惜傲醉,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呻率。 院中可真熱鬧硬毕,春花似錦、人聲如沸礼仗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽元践。三九已至韭脊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間单旁,已是汗流浹背沪羔。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留象浑,地道東北人蔫饰。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓切蟋,卻偏偏與公主長得像鹃唯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子削咆,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344