递归锁RECURSIVE_MUTEX的原理以及使用
标签: Multi_Thread Recursive_Mutex
递归锁的作用
MutexLock mutex;
void foo()
{
mutex.lock();
// do something
mutex.unlock();
}
void bar()
{
mutex.lock();
// do something
foo();
mutex.unlock();
}
foo函数和bar函数都获取了同一个锁,而bar函数又会调用foo函数。如果MutexLock锁是个非递归锁,则这个程序会立即死锁。因此在为一段程序加锁时要格外小心,否则很容易因为这种调用关系而造成死锁。
不要存在侥幸心理,觉得这种情况是很少出现的。当代码复杂到一定程度,被多个人维护,调用关系错综复杂时,程序中很容易犯这样的错误。庆幸的是,这种原因造成的死锁很容易被排除。
但是这并不意味着应该用递归锁去代替非递归锁。递归锁用起来固然简单,但往往会隐藏某些代码问题。比如调用函数和被调用函数以为自己拿到了锁,都在修改同一个对象,这时就很容易出现问题。因此在能使用非递归锁的情况下,应该尽量使用非递归锁,因为死锁相对来说,更容易通过调试发现。程序设计如果有问题,应该暴露的越早越好。
自制递归锁
#include"stdafx.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <stdio.h>
#include <Windows.h>
using namespace std;
int g_num = 0; // 为 g_num_mutex 所保护
recursive_mutex g_num_mutex;
class RecursiveMutex {
public:
RecursiveMutex() {
num_of_locks = 0;
}
~RecursiveMutex() {
}
void lock() {
if (num_of_locks == 0) {
my_mutex.lock();
owner_thread_id = GetCurrentThreadId();
}
else if (GetCurrentThreadId() == owner_thread_id)
num_of_locks++;
}
void unlock() {
if (num_of_locks > 0)
num_of_locks--;
if (num_of_locks == 0)
my_mutex.unlock();
}
private:
int num_of_locks;
mutex my_mutex;
int owner_thread_id;
};
RecursiveMutex myRecursiveMutex;
void slow_increment_stl(int id)
{
for (int i = 0; i < 3; ++i) {
g_num_mutex.lock();
++g_num;
std::cout << id << " => " << g_num << '\n';
g_num_mutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
void slow_increment(int id)
{
for (int i = 0; i < 3; ++i) {
myRecursiveMutex.lock();
++g_num;
std::cout << id << " => " << g_num << '\n';
myRecursiveMutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
int main()
{
cout << "使用stl中的recursive_mutex" << endl;
std::thread t1(slow_increment_stl, 0);
std::thread t2(slow_increment_stl, 1);
t1.join();
t2.join();
cout << "使用自写类RecursiveMutex" << endl;
std::thread t3(slow_increment, 0);
std::thread t4(slow_increment, 1);
t3.join();
t4.join();
}
可见使用自制递归锁类与标准库中的递归锁类输出相同。
问题就是:加锁的操作需要相互嵌套,如果使用std::mutex 肯定会导致死锁,而重构代码,提取出共用部分的工作量又很大。
这个时候我发现了好东西 std::recursive_mutex 递归锁
递归锁可以允许一个线程对同一互斥量多次加锁,解锁时,需要调用与lock()相同次数的unlock()才能释放使用权
这边再介绍一个好东西:
std::lock_guard<std::recursive_mutex>
std::lock_guard在构造函数中加锁,在析构函数中解锁,利用这个类可以减少我们对加锁可解锁操作的管理工作,专注于逻辑实现。
lock_guard类结构如下
template<typename _Mutex>
class lock_guard
{
public:
typedef _Mutex mutex_type;
explicit lock_guard(mutex_type& __m) : _M_device(__m)
{ _M_device.lock(); }
lock_guard(mutex_type& __m, adopt_lock_t) : _M_device(__m)
{ } // calling thread owns mutex
~lock_guard()
{ _M_device.unlock(); }
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
mutex_type& _M_device;
};
可以很清楚的看到加锁和解锁过程