基本概念
可重入锁(Reentrant Lock),是指允许同一个线程多次对该锁进行acquire动作。对于不可重入的锁,当一个线程多次调用acquire后将造成死锁。可重入锁具有广泛的应用,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| struct Routine
{
void f()
{
lock_.lock();
h();
g();
lock_.unlock();
}
void g()
{
lock_.lock();
cout<<"abc"<<endl;
lock_.unlock();
}
void h()
{
lock_.lock();
cout<<"def"<<endl;
lock_.unlock();
}
Lock lock_;
};
|
在函数f里调用了g和h,而在每个函数里都试图对lock进行acquire操作。可能你会说保证只有一个函数加锁就行了,但是有时候很难做成这个样子,毕竟一般我们都要求任何一个public函数被调用时候都得保证是线程安全因此需要加锁的。
那么,reentrant lock和recursive lock有什么区别呢?根据wikipedia,这两个是一样的。
基本实现
自己实现一个可重入锁并不困难:我们增加两个field,一个用来记录锁当前被哪个线程拥有;一个用来记录尝试acquire的次数。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| #include <stdint.h>
#include <pthread.h>
Class MyReentrantLock
{
public:
MyReentrantLock()
{
lock_holder_ = NULL;
hold_counter_ = 0;
pthread_mutex_init(&lock_, NULL);
}
int lock()
{
int ret = 0;
pthread_t curr_id = pthread_self();
if (lock_holder_ == curr_id) {
++hold_counter_;
} else {
pthread_mutex_lock(&lock_);
lock_holder_ = curr_id;
hold_counter_ = 1;
}
return ret;
}
int unlock()
{
int ret = 0;
pthread_t curr_id = pthread_self();
if (lock_holder_ != curr_id) {
ret = -1;
} else {
if (--hold_counter_ == 0) {
lock_holder_ = NULL;
pthread_mutex_unlock(&lock_);
}
}
return ret;
}
private:
pthread_mutex_t lock_;
pthread_t lock_holder_;
int64_t hold_counter_;
};
|
顺便说一句,默认pthread_mutex_t是不可重入的。为了让它可重入,可以这样:
1
2
3
4
5
| pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//设置可递归也就是可重入
pthread_mutex_init(&mutex, &attr);
|
参考文献
1,《The Art Of Multiprocessor Programming》 8.4节,187页
这本书的作者都是并发编程大仙级人物。虽然是用Java语言写的,但是还是很值得一读。
2,Implementing a Recursive Mutex
很好的博客,每篇都非常高水准。我们最近正在翻译其中和并发编程相关的文章。可以点击这里查看:深入探索并发编程系列