线程的互斥和同步(8)- C++11中的互斥锁和条件变量

文章目录

 

1. 互斥锁

之前讲过使用Windows的API和Qt中如何创建和使用互斥锁。接下来,主要说明一下C++11中的互斥锁。
c++11中的互斥锁主要有如下几种:

互斥锁 说明
mutex 最基本的互斥锁,不可重入
timed_mutex 具有超时机制的互斥锁
recursive_mutex 可重入的互斥锁
recursive_timed_mutex 结合 timed_mutex 和 recursive_mutex 特点的互斥量

他们都有加锁(lock)、尝试加锁(trylock)和解锁(unlock)的方法。

 

(1) 递归锁和非递归锁

这里说明一下 递归锁非递归锁

  • 递归锁 ,可重入锁。表示同一线程中,可以多次加锁,要解锁相应的次数才能够解锁。
  • 非递归锁 ,不可重入。表示如果多次加锁,会阻塞导致线程死锁。

Windows API CreateCreateMutex 和临界区都属于 递归锁
而Qt中的 QMutex 创建时默认是 非递归的锁 ,构造中传入参数 Recursive (可递归),如果不传则默认为 NonRecursive (非递归)。

接下来举一个例子来说明:

如果在线程中,调用函数 func() 就会引起线程 死锁 ,因为它加锁两次。
而使用 std::recursive_mutex 替换 std::mutex 就没有死锁的问题。
但是,这并不意味着,我们就需要使用 递归锁 ,使用 非递归锁 更容易定位我们程序中的问题。

 

(2) 互斥锁管理类

之前我们在j讲 QMutex 的时候,同时讲解了RALL的 QMutexLocker ;C++11中也提供了类似的方法。
Qt中的互斥锁可参照:线程的互斥和同步(4)- Qt中的互斥锁(QMutex和QMutexLocker)

互斥锁管理类 说明
lock_guard 基于作用域的互斥量管理
unique_lock 更加灵活的互斥量管理
  • lock_guard , 没有 lockunlock 仅提供构造时加锁,析构时解锁。
  • unique_lock , 允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

比如可以这么使用一个互斥锁管理器:

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


 

2. std::condition_variable

之前讲了使用Qt创建和使用条件变量,C++11中也提供了对于条件变量的操作方法
线程的互斥和同步(7)- Qt的条件变量QWaitCondition

它同样提供了wait和唤醒的方法

  • 等待:waitwait_forwait_until
  • 唤醒:notify_one (唤醒一个) 和 notify_all (唤醒全部)

这里需要注意的是 虚假唤醒
虚假唤醒 是指可能一个线程没有发送唤醒通知,但是该线程已经被唤醒。

通常防止虚假唤醒的做法是,使用一个 while 循环,如果是虚假唤醒,则继续等待:

也可以写成如下方式:

此处的 wait 函数的第二个参数,为一个仿函数(lambda表达式就是一个匿名仿函数),如果返回值为 true 则忽略虚假唤醒;否则继续等待。

关于 std::condition_variable 更多介绍,可参考:
https://zh.cppreference.com/w/cpp/thread/condition_variable

You May Also Like

About the Author: admin

喜欢编程、爱游戏,更爱生活。

发表评论

电子邮件地址不会被公开。