这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
<mutex>头文件中为我们提供了三种mutex包装类,分别是
- lock_guard,提供了基于作用域的互斥锁包装类
- unique_lock,提供了支持移动的互斥锁包装类
- scoped_lock,支持多个互斥锁同时上锁避免死锁的互斥锁包装类
lock_guard
示例
lock_guard基本的用法如下:
void Speaking::speak_lock_guard() {
std::lock_guard<std::mutex> lock(m);
speak_without_lock();
}
lock_guard将所在的作用域视为临界区,由于c++是块作用域,如果在一个函数中使用lock_guard,则会将整个函数上锁,通常来说,为了只为临界区上锁,可以通过大括号构建出一个块作用域,如下:
void Speaking::speak_lock_guard() {
std::cout << "not lock" << std::endl;
{
std::lock_guard<std::mutex> lock(m);
speak_without_lock();
}
}
原理剖析
我们来看一下lock_guard的源码,其原理一目了然:
/** @brief A simple scoped lock type.
*
* A lock_guard controls mutex ownership within a scope, releasing
* ownership in the destructor.
*/
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) noexcept : _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;
};
其原理十分简单,在构造时使用mutex上锁,在析构函数中将其释放,这样就达到了在作用域范围内上锁的功能。
unique_lock
unique_lock提供了通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。unique_lock和lock_guard最大的区别在于支持移动语义,可以将某个锁的所有权移动到其他unique_lock。
除了提供mutex的相关方法,还提供其他的一些方法,用于操作和观察当前mutex的状态。
// 修改器
void swap(unique_lock& u) noexcept; // 与另一 std::unique_lock 交换状态
mutex_type* release() noexcept; // 将关联互斥锁解关联而不解锁它
// 观察器
bool owns_lock() const noexcept; // 测试锁是否占有其关联的互斥锁
explicit operator bool () const noexcept; // 测试锁是否占有其关联的互斥锁
mutex_type* mutex() const noexcept; // 返回指向关联互斥锁的指针
原理剖析
我们来看一下移动语义是如何支持的:
explicit unique_lock(mutex_type& __m)
: _M_device(std::__addressof(__m)), _M_owns(false) {
lock();
_M_owns = true;
}
unique_lock(const unique_lock&) = delete;
unique_lock& operator=(const unique_lock&) = delete;
unique_lock(unique_lock&& __u) noexcept
: _M_device(__u._M_device), _M_owns(__u._M_owns) {
__u._M_device = 0;
__u._M_owns = false;
}
unique_lock& operator=(unique_lock&& __u) noexcept {
if (_M_owns) unlock();
unique_lock(std::move(__u)).swap(*this);
__u._M_device = 0;
__u._M_owns = false;
return *this;
}
private:
mutex_type* _M_device;
bool _M_owns;
首先unique_lock在构造时,将关联的互斥锁的指针存在_M_device中,并直接上了锁。
unique_lock的拷贝构造函数和赋值运算符都是不可用的
移动构造函数中,直接将传入的右值引用中的互斥锁指针和own标识存下,并将传入的右值引用中的数据清空
移动赋值运算符中,首先将自己的锁unlock,然后使用右值引用重新赋值了自己。
scoped_lock
类 scoped_lock
是提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。
创建 scoped_lock
对象时,它试图取得给定互斥的所有权。控制离开创建 scoped_lock
对象的作用域时,析构 scoped_lock
并以逆序释放互斥。若给出数个互斥,则使用免死锁算法
scoped_lock 类不可复制。
小结
本文介绍分析了mutex头文件中mutex的包装类,使用包装类操作mutex可以有效的避免我们错误使用mutex而导致程序的死锁。