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。都可在苹果的开源代码库下载到。

相关文章
相关标签/搜索