前言:
线程间同步的机制很多,最常用的就是各种锁,比如 c++ 11 标准中的 mutex 和 recursive_mutex , 比如 Win32 的 CRITICAL_SECTION 和 Mutex 。 c++开发中我们经常会做一些适用性封装,这也会引入一些潜在的风险,如果不注意的话便可能导致死锁。最常见的就是单线程重复加锁会导致死锁问题。
代码:
// ConsoleApplication12.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <Windows.h>
#include <iostream>
#include <Mutex>
#include <mutex>
using namespace std;
//window Mutex
HANDLE h_m;
//c++ 11 mutex
mutex mu;
//windows CRITICAL_SECTION
CRITICAL_SECTION h_cs;
//c++ 11 recursive_mutex
recursive_mutex r_mu;
//CRITICAL_SECTION NEED INITIALIZATION
int dummy = [](CRITICAL_SECTION& cs)->int {
InitializeCriticalSection(&cs);
return 0;
}(h_cs);
void funcB();
void funcA()
{
//Mutex - no dead lock
//WaitForSingleObject(h_m, INFINITE);
//c++ 11 lock_gurad<mutex> - dead lock
//lock_guard<mutex> lock(mu);
//c++ 11 mutex - dead lock
//mu.lock();
//CRITICAL_SECTION - no dead lock
//EnterCriticalSection(&h_cs);
//c++ 11 recursive_mutex - debug mode --> assert is triggered
// release mode --> OK
//r_mu.lock();
//c++ 11 lock_gurad<recursive_mutex> - no dead lock
lock_guard<recursive_mutex> lock(r_mu);
funcB();
}
void funcB()
{
//Mutex - no dead lock
//WaitForSingleObject(h_m, INFINITE);
//c++ 11 lock_gurad<mutex> - dead lock
//lock_guard<mutex> lock(mu);
//c++ 11 mutex - dead lock
//mu.lock();
//CRITICAL_SECTION - no dead lock
//EnterCriticalSection(&h_cs);
//c++ 11 recursive_mutex - debug mode --> assert is triggered
// release mode --> OK
//r_mu.lock();
//c++ 11 lock_gurad<recursive_mutex> - no dead lock
lock_guard<recursive_mutex> lock(r_mu);
}
int main()
{
h_m = CreateMutex(NULL,FALSE,NULL);
funcA();
std::cout << "Hello World!\n";
}
小结:
1)如果使用c++ 11进行开发,那么在使用自释放锁 lock_guard 的时候,请配合 recursive_mutex 使用,因为 mutex 有死锁风险,代价就是可能稍微慢那么一点点,不过如果真的要追求效率,请使用CRITICAL_SECTION并自行封装一个自释放锁。
2)CRITICAL_SECTION 是可以单线程重复加锁的,只要在保证有相同数量的解锁 动作即可保证安全。
3)Windows 下的 Mutex 同样可以单线程重复加锁,保证解锁 次数 和加锁次数一致即可保证安全。
4)c++ 11 的mutex 使用一定要注意,单线程重复加锁必定 死锁,其作为 lock_guard 的模板参数也是一样死锁。
5)DEBUG 模式下 recursive_mutex 单线程重复加锁会触发 assert 语句,RELEASE 模式下工作正常。
6)在c++ 工程中,极度不建议使用 mutex , 因为大型工程中很多第三方库通过回调函数的形式进行函数调用,这及其可能导致单线程调用循环,从而导致某个锁被加锁两次而触发死锁。