之前讲过使用Windows的API和Qt中如何创建和使用互斥锁。接下来,主要说明一下C++11中的互斥锁。
c++11中的互斥锁主要有如下几种:
互斥锁 | 说明 |
---|---|
mutex | 最基本的互斥锁,不可重入 |
timed_mutex | 具有超时机制的互斥锁 |
recursive_mutex | 可重入的互斥锁 |
recursive_timed_mutex | 结合 timed_mutex 和 recursive_mutex 特点的互斥量 |
他们都有加锁(lock)、尝试加锁(trylock)和解锁(unlock)的方法。
这里说明一下 递归锁 和 非递归锁
Windows API CreateCreateMutex 和临界区都属于 递归锁 。
而Qt中的 QMutex 创建时默认是 非递归的锁 ,构造中传入参数
Recursive (可递归),如果不传则默认为 NonRecursive (非递归)。
接下来举一个例子来说明:
std::mutex g_mutex;
int g_number = 0;
void func2(void);
void func(void)
{
g_mutex.lock();
++g_number;
func2();
g_mutex.unlock();
}
void func2(void)
{
g_mutex.lock();
++g_number;
g_mutex.unlock();
}
如果在线程中,调用函数 func() 就会引起线程 死锁 ,因为它加锁两次。
而使用 std::recursive_mutex 替换 std::mutex 就没有死锁的问题。
但是,这并不意味着,我们就需要使用 递归锁 ,使用 非递归锁 更容易定位我们程序中的问题。
之前我们在讲 QMutex 的时候,同时讲解了RALL的 QMutexLocker ;C++11中也提供了类似的方法。
Qt中的互斥锁可参照:线程的互斥和同步(4)- Qt中的互斥锁(QMutex和QMutexLocker)
互斥锁管理类 | 说明 |
---|---|
lock_guard | 基于作用域的互斥量管理 |
unique_lock | 更加灵活的互斥量管理 |
比如可以这么使用一个互斥锁管理器:
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex, std::defer_lock);
locker.lock();
++g_number;
}
locker 在构造的时候可以指定为构造时加锁还是不加锁。
这里使用 std::defer_lock 表明构造的时候不加锁,使用 adopt_lock 表示构造的时候同时加锁。
析构时,根据当前的锁属性判断是否解锁,如果当前为加锁状态,析构时就解锁。
unique_lock 它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
同时还有方法 swap (与另一 std::unique_lock 交换状态) 和 release (将关联互斥解关联而不解锁它)
关于 unique_lock 的更多使用,可参照:
https://zh.cppreference.com/w/cpp/thread/unique_lock
之前讲了使用Qt创建和使用条件变量,C++11中也提供了对于条件变量的操作方法
线程的互斥和同步(7)- Qt的条件变量QWaitCondition
它同样提供了wait和唤醒的方法
通常防止虚假唤醒的做法是,使用一个 while 循环,如果是虚假唤醒,则继续等待:
std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex);
// 循环等待,防止虚假唤醒
while (!g_pred)
{
// 当唤醒时,g_pred 将设置为true
var.wait(locker);
}
}
也可以写成如下方式:
std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex);
// 防止虚假唤醒
var.wait(locker, [] {return g_pred; });
}
此处的 wait 函数的第二个参数,为一个仿函数(lambda表达式就是一个匿名仿函数), 如果返回值为 true 则忽略虚假唤醒;否则继续等待。
关于 std::condition_variable 更多介绍,可参考:
https://zh.cppreference.com/w/cpp/thread/condition_variable