iOS 原理探究-互斥鎖

在編程中,引入了對象互斥鎖的概念,來保證共享數據操做的完整性。每一個對象都對應於一個可稱爲" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。對於互斥鎖,若是資源已經被佔用,資源申請者只能進入睡眠狀態。編程

互斥鎖又分爲遞歸鎖和非遞歸鎖。swift

  • 遞歸鎖是一種能夠屢次被同一線程持有的鎖。
  • 非遞歸鎖是隻能一個線程鎖定一次,想要再次鎖定,就必須先要解鎖,不然就會發生死鎖現象。

非遞歸鎖

pthread_mutex

建立和銷燬

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//靜態初始化互斥量
int pthread_mutex_init(pthread_mutex_t*mutex,pthread_mutexattr_t*attr);//動態初始化互斥量
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`給互斥量加鎖。當另外一個線程來獲取這個鎖的時候,發現這個鎖已經加鎖,那麼這個線程就會進入休眠狀態,直到這個互斥量被解鎖,線程纔會從新被喚醒。
  • pthread_mutex_trylock當互斥量已經被鎖住時調用該函數將返回錯誤代碼EBUSY,若是當前互斥量沒有被鎖,則會執行和pthread_mutex_lock同樣的效果。
  • pthread_mutex_unlock對互斥量進行解鎖。

NSLock

咱們在swift版本開源的CoreFoundation框架下咱們能夠看到關於NSLock的完整定義。安全

注意:如下代碼只摘取了關鍵部分,其餘兼容性代碼已被省略。bash

建立

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:
}
複製代碼

這個函數的主要做用就是在某一個時間點以前不斷的嘗試加鎖,主要作了下面幾件事情。框架

  • pthread_mutex_trylock嘗試加鎖,若是加鎖成功則直接返回true
  • timedLock經過一層while循環調用pthread_mutex_trylock不斷嘗試加鎖。若是失敗失敗則返回false,若是成功則返回true

經過上述代碼咱們能夠發現NSLock底層呢,其實也就是基於pthread_mutex進行了一次面向對象的封裝。由於他是比pthread_mutex更高一級的API,因此在性能方面呢比pthread_mutex稍微差一點。ide

NSCondition

NSConditionNSLock同樣都是基於pthread_mutex進行面向對象分裝的一個非遞歸的互斥鎖,在swift版本開源的CoreFoundation框架下咱們一樣能夠找到相關的開源代碼。函數

條件對象既充當給定線程中的鎖又充當檢查點。鎖在測試條件並執行條件觸發的任務時保護您的代碼。檢查點行爲要求條件在線程繼續執行其任務以前爲真。條件不成立時,線程將阻塞。它保持阻塞狀態,直到另外一個線程向條件對象發出信號爲止。性能

注意:如下代碼只摘取了關鍵部分,其餘兼容性代碼已被省略。測試

建立

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)
}
複製代碼

它在NSLock的基礎上又增長了些新的功能。ui

等待

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);
}
複製代碼

發信號通知條件變量,僅喚醒一個線程。

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);
}
複製代碼

蘋果的註釋告訴咱們這是一個(發信號通知條件變量,喚醒全部等待它的線程。)的函數。

NSConditionLock

使用NSConditionLock對象,能夠確保線程僅在知足特定條件時才能獲取鎖。 一旦得到了鎖並執行了臨界區的代碼,線程就能夠放棄該鎖並將關聯條件設置爲新的條件。 條件自己是任意的:您能夠根據應用程序的須要定義它們。

NSConditionLockNSCondition的升級版,其內部持有了一個條件鎖NSCondition

internal var _cond = NSCondition()//內存持有的條件鎖
internal var _value: Int//條件變量
internal var _thread: _swift_CFThreadRef?//當前的線程
複製代碼

加鎖

open func lock() {
		let _ = lock(before: Date.distantFuture)
}
複製代碼

其內部是調用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
}
複製代碼

這個函數作了如下幾件事情。

  • 首先調用條件鎖加鎖,保證接下來代碼的線程安全性。而後判斷當前是否有線程正在運行。
  • 若是有線程正在運行,而後調用條件鎖阻塞當前線程。若是條件不成立,獲取當前正在運行的線程賦值_thread屬性,而後對條件鎖進行一個unlock解鎖的操做。
  • 這裏主要是保障條件變量的線程安全性。

解鎖

open func unlock() {
        _cond.lock()
#if os(Windows)
        _thread = INVALID_HANDLE_VALUE
#else
        _thread = nil
#endif
        _cond.broadcast()
        _cond.unlock()
    }

複製代碼
  • 首先調用NSConditionlock方法給接下來的臨界區加鎖。

  • _thread屬性置爲nil

  • 發信號通知條件變量,喚醒全部等待它的線程。

  • 調用NSConditionunlock方法解鎖。

lockWhenCondition:

open func lock(whenCondition condition: Int) {
		let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
複製代碼

其內部調用了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實現原理差很少,只是多了一個_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()
    }
複製代碼

這裏的實現邏輯和unlock的相同,只是多了一步修改條件變量的步驟_value = condition

tryLock

open func tryLock(whenCondition condition: Int) -> Bool {
		return lock(whenCondition: condition, before: Date.distantPast)
}
複製代碼

lockWhenCondition同樣內部都是經過調用lock(whenCondition condition: Int, before limit: Date) -> Bool實現的。

遞歸鎖

NSRecursiveLock

咱們有幸獲得了NSRecursiveLock底層源碼,咱們一塊兒來看看它是如何實現遞歸的。

注意:如下代碼只截取了關鍵部分。

建立

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))這是一句關鍵性的代碼,經過這個type來區分是不是遞歸鎖。
#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)
}
複製代碼

底層是調用了pthread加鎖函數。

解鎖

open func unlock() {
    pthread_mutex_unlock(mutex)
}
複製代碼

銷燬

deinit {
    pthread_mutex_destroy(mutex)
}
複製代碼

總結

NSRecursiveLock底層就是對pthread_mutex的封裝,經過設置標識來區分是否爲遞歸鎖。

遞歸鎖的實現原理

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以此查看源碼,看是否能找到調用_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中,找到了這個函數。

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;
}
複製代碼

在這裏咱們發現若是是遞歸鎖,那麼這把鎖維護了一個lock_count的引用計數。每加一次鎖都會對引用計數執行+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;
	}
}
複製代碼

接下來會跳轉到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;
}
複製代碼

咱們發現當res爲0,也就是肯定是遞歸鎖的時候,會對當前的鎖進行一次持有。能夠理解爲對鎖的引用計數加1。

遞歸解鎖的流程

pthread_mutex_unlock->_pthread_mutex_firstfit_unlock_slow->_pthread_mutex_firstfit_unlock_updatebits->_pthread_mutex_unlock_handle_options
複製代碼

同理,咱們發現當咱們在調用解鎖的函數時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返回的結果大於0的時候進行正常的遞歸解鎖操做。能夠理解爲鎖的引用計數加1。

總結

遞歸鎖咱們能夠簡單理解爲是一個OC對象,它擁有一個引用計數的屬性,當咱們進行多層次的遞歸加鎖時,引用計數會加一,每釋放一層鎖的時候,引用計數會加一,當引用計數爲0的時候,這個遞歸鎖就會被釋放。等待下一個線程對他的持有。

總結

這裏介紹了基於pthread_mutex實現的遞歸鎖以及非遞歸鎖。

  • 非遞歸鎖裏面NSLock是基於pthread_mutex實現的。

  • 條件鎖NSCondition也是基於pthread_mutex實現的,基礎功能和NSLock類似。在NSLock的基礎上增長了一些功能。

  • 條件鎖NSConditionLock是基於NSCondition實現的,在其基礎上進一步的增長了一下其餘功能。

  • 遞歸鎖NSRecursiveLock也是基於pthread_mutex實現的。運用了引用計數的思想。經過維護引用計數來達到一個加解鎖平衡的狀態。

  • 以上代碼的分析來自swift版本的CoreFoundationpthread。都可在蘋果的開源代碼庫下載到。

相關文章
相關標籤/搜索