在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。
互斥锁又分为递归锁和非递归锁。
- 递归锁是一种可以多次被同一线程持有的锁。
- 非递归锁是只能一个线程锁定一次,想要再次锁定,就必须先要解锁,否则就会发生死锁现象。
非递归锁
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
的完整定义。
注意:以下代码只摘取了关键部分,其他兼容性代码已被省略。
创建
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
稍微差一点。
NSCondition
NSCondition
和NSLock
一样都是基于pthread_mutex
进行面向对象分装的一个非递归的互斥锁,在swift
版本开源的CoreFoundation
框架下我们同样可以找到相关的开源代码。
条件对象既充当给定线程中的锁又充当检查点。锁在测试条件并执行条件触发的任务时保护您的代码。检查点行为要求条件在线程继续执行其任务之前为真。条件不成立时,线程将阻塞。它保持阻塞状态,直到另一个线程向条件对象发出信号为止。
注意:以下代码只摘取了关键部分,其他兼容性代码已被省略。
创建
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
的基础上又增加了些新的功能。
等待
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
对象,可以确保线程仅在满足特定条件时才能获取锁。 一旦获得了锁并执行了临界区的代码,线程就可以放弃该锁并将关联条件设置为新的条件。 条件本身是任意的:您可以根据应用程序的需要定义它们。
NSConditionLock
是NSCondition
的升级版,其内部持有了一个条件锁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()
}
-
首先调用
NSCondition
的lock
方法给接下来的临界区加锁。 -
对
_thread
属性置为nil
。 -
发信号通知条件变量,唤醒所有等待它的线程。
-
调用
NSCondition
的unlock
方法解锁。
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版本的
CoreFoundation
和pthread
。均可在苹果的开源代码库下载到。
网友评论