这是并发控制方案的系列文章,介绍了各种锁的使用及优缺点。
OSSpinLock、os_unfair_lock、pthread_mutex_t、pthread_cond_t、pthread_rwlock_t 是值类型,不是引用类型。这意味着使用 = 会进行复制,使用复制的可能导致闪退。pthread 函数认为其一直处于初始化的内存地址,将其移动到其他内存地址会产生问题。使用copy的OSSpinLock不会崩溃,但会得到一个全新的锁。
如果你对线程、进程、串行、并发、并行、锁等概念还不了解,建议先查看以下文章:
递归锁(Recursive Lock)也称为可重入互斥锁(reentrant mutex),是互斥锁的一种,同一线程对其多次加锁不会产生死锁。递归锁会使用引用计数机制,以便可以从同一线程多次加锁、解锁,当加锁、解锁次数相等时,锁才可以被其他线程获取。
上一篇文章介绍的
pthread_mutex_t
和NSLock
,在同一线程多次加锁,均会导致死锁。
这篇文章包含两种实现互斥锁的方案:pthread_mutex_t
和NSRecursiveLock
。
1. pthread_mutex_t
在上一篇文章互斥锁部分已经介绍过pthread_mutex_t
。当 mutex type 为PTHREAD_MUTEX_RECURSIVE
时,mutex 为递归锁。
如果你对
pthread_mutex_t
不了解,推荐查看:线程同步之互斥锁
当线程首次获取到锁时,锁计数为1。该线程每加锁一次,计数加一。该线程解锁一次,计数减一。当计数为0时,锁将可被其他线程获取。如果线程尝试解锁未加锁的锁,或者该线程未获取到的锁,会返回错误。
1.1 初始化锁pthread_mutex_init()
初始化锁前需先初始化pthread_mutexattr_t
,并设定pthread_mutexattr_settype()
:
// 初始化属性
var attr = pthread_mutexattr_t()
pthread_mutexattr_init(&attr)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)
初始化锁方法如下:
private var recursiveMutex = pthread_mutex_t()
override init() {
// 初始化锁
pthread_mutex_init(&recursiveMutex, &attr)
}
不再使用的属性需要销毁:
// 销毁属性
pthread_mutexattr_destroy(&attr)
1.2 加锁、解锁
这里的加锁、解锁方法与互斥锁一致。
override func otherTest() {
pthread_mutex_lock(&recursiveMutex)
struct Holder {
static var count = 0
}
Holder.count += 1
print("count = \(Holder.count)")
if Holder.count < 10 {
self.otherTest()
}
pthread_mutex_unlock(&recursiveMutex)
}
2.3 销毁锁pthread_mutex_destroy()
销毁递归锁与销毁互斥锁方法一致。
deinit {
pthread_mutex_destroy(&recursiveMutex)
}
3. NSRecursiveLock
NSRecursiveLock
使用 POSIX thread 实现锁,是对 pthread_mutex_t 的封装。NSRecursiveLock
也遵守了NSLocking协议。
3.1 初始化锁NSRecursiveLock()
使用以下方法初始化锁:
private var recursiveLock = NSRecursiveLock()
在GNUstep的NSLock.m
文件中,其初始化代码如下:
- (id) init
{
if (nil != (self = [super init]))
{
if (0 != pthread_mutex_init(&_mutex, &attr_recursive))
{
DESTROY(self);
}
}
return self;
}
3.2 加锁lock()
NSRecursiveLock
类有lock()
、lock(before:)
、try()
三种方式初始化锁,与NSLock
相同。
这里使用lock()
加锁:
recursiveLock.lock()
在 GNUstep 的NSLock.m
文件中,上述三种加锁方法代码如下:
#define MLOCK \
- (void) lock\
{\
int err = pthread_mutex_lock(&_mutex);\
if (EDEADLK == err)\
{\
(*_NSLock_error_handler)(self, _cmd, YES, @"deadlock");\
}\
else if (err != 0)\
{\
[NSException raise: NSLockException format: @"failed to lock mutex"];\
}\
}
#define MLOCKBEFOREDATE \
- (BOOL) lockBeforeDate: (NSDate*)limit\
{\
do\
{\
int err = pthread_mutex_trylock(&_mutex);\
if (0 == err)\
{\
CHK(Hold) \
return YES;\
}\
sched_yield();\
} while ([limit timeIntervalSinceNow] > 0);\
return NO;\
}
#define MTRYLOCK \
- (BOOL) tryLock\
{\
int err = pthread_mutex_trylock(&_mutex);\
if (0 == err) \
{ \
CHK(Hold) \
return YES; \
} \
else \
{ \
return NO;\
} \
}
3.3 解锁unlock()
必须在加锁的线程调用unlock()
解锁,在其他线程解锁会产生无法预期的错误,解锁未获取到的锁也会产生错误。
override func otherTest() {
recursiveLock.lock()
struct Holder {
static var count = 0
}
Holder.count += 1
print("count = \(Holder.count)")
if Holder.count < 10 {
self.otherTest()
}
recursiveLock.unlock()
}
在 GNUstep 的NSLock.m
文件中,unlock()
代码如下:
#define MUNLOCK \
- (void) unlock\
{\
if (0 != pthread_mutex_unlock(&_mutex))\
{\
[NSException raise: NSLockException\
format: @"failed to unlock mutex"];\
}\
CHK(Drop) \
}
上述加锁、解锁方式与
NSLock
完全一致,只是锁不同。
Demo名称:Synchronization
源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/Synchronization
上一篇:线程同步之互斥锁
下一篇:线程同步之条件锁
参考资料:
欢迎更多指正:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/线程同步之递归锁.md