1. std::mutex:独占的互斥量,不能递归使用。下面是它的类的部分定义:
class mutex
{
public:
// std::mutex不支持拷贝和赋值操作。
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
constexpr mutex() noexcept; // 构造函数:新的对象是未锁的
~mutex();
public:
void lock(); // 上锁。会有三种情况
// (1) 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁
// (2) 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
// (3)如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
void unlock(); // 解锁
bool try_lock(); // 尝试上锁。成功,返回true。失败时返回false,但不阻塞。会有三种情况
// (1) 如果当前互斥量没被其他线程占有,则锁住互斥量,直到该线程调用unlock
// (2) 如果当前互斥量被其他线程占用,则调用线程返回false,且不会被阻塞
// (3) 如果互斥量己被当前线程锁住,则会产生死锁
};
为什么有些类会去禁止拷贝和赋值呢?主要是防止浅拷贝的问题,因为类中如果有指针的话,浅拷贝方式的结果是两个不同对象的指针
指向同一块内存区域,容易出现访问冲突,多次delete等错误,不是我们所希望的。
1)互斥量不允许拷贝,也不允许移动。新创建的互斥量对象是未上锁的。
2)lock和unlock必须成对出现,否则可能引起未定义行为。
注:实践中不推荐直接去调用成员函数lock(),调用lock()就意味着,必须在每个函数出口都要去调用unlock(),也包括异常的情况。
如果程序员没有进行unlock或者因为异常无法unlock,那么系统就会发生死锁。针对这个问题,C++11中引入了std::unique_lock
与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装(只是包装,真正的加锁和解锁还都是mutex完成的),
实现自动unlock的功能。
2. std::lock_guard:C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard,在构造时就能提供已锁的互斥量,并在析构的时候
进行解锁,从而保证了一个已锁互斥量能被正确解锁(自解锁),不会因为某个线程异常退出而影响其他线程。下面是它的类的部分定义。
struct adopt_lock_t {}; // 空的标记类
constexpr adopt_lock_t adopt_lock {}; // 常量对象
template <class _Mutex>
class lock_guard
{
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { _MyMutex.lock(); } // 构造,并加锁
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {} // 只构造,不加锁
~lock_guard() noexcept { _MyMutex.unlock(); } // unlock
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
1)lock_guard对象不可拷贝和移动。
2)它有两个重载的构造函数,其中lock_gurad(_Mutex&)会自动对_Mutex进行加锁,而lock_gurad(_Mutex&,adopt_lock_t)则只构造
但不加锁,因此需要在某个时候通过调用_Mutex本身的lock()进行上锁。(说明:adopt_lock_t是个空的标签类,起到通过标签来重载构造函数的作用)。
3)在lock_gurad对象的生命周期内,它所管理的Mutex对象会一直保持上锁状态,直至生命周期结束后才被解锁。不需要,也无法手动通过lock_gurad对
Mutex进行上锁和解锁操作。从总体上而言,没有给程序员提供足够的灵活度来对互斥量的进行上锁和解锁控制。
3. std::unique_lock:std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更
灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。下面是它的类的部分定义:
// 空的标记类
struct adopt_lock_t {};
struct defer_lock_t {};
struct try_to_lock_t {};
// 常量对象
constexpr adopt_lock_t adopt_lock {};
constexpr defer_lock_t defer_lock {};
constexpr try_to_lock_t try_to_lock {};
template <class _Mutex>
class unique_lock { // 在析构函数中自动解锁mutex
public:
using mutex_type = _Mutex;
unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) { // 默认构造函数
}
explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // 构造并上锁。
_Pmtx->lock(); // 如果其他unique_lock己拥有该_Mtx,则会阻塞等待
_Owns = true; // 成功获取锁,拥有锁的所有权。
}
unique_lock(_Mutex& _Mtx, adopt_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // 构造,并假定己上锁(mutex需要在外面事先被锁住)。注意拥有锁的所有权
}
unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
: _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // 构造,但不上锁。false表示并未取得锁的所有权。
}
unique_lock(_Mutex& _Mtx, try_to_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) { // 构造,并尝试上锁。如果上锁不成功,并不会阻塞当前线程
}
//支持移动构造
unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // 移动拷贝,destructive copy
_Other._Pmtx = nullptr; // 失去对原mutex的所有权
_Other._Owns = false;
}
//支持移动赋值
unique_lock& operator=(unique_lock&& _Other) { // 移动赋值, destructive copy
if (this != _STD addressof(_Other)) { // different, move contents
if (_Owns) {
_Pmtx->unlock();
}
_Pmtx = _Other._Pmtx;
_Owns = _Other._Owns;
_Other._Pmtx = nullptr;
_Other._Owns = false;
}
return *this;
}
~unique_lock() noexcept { // clean up
if (_Owns) {
_Pmtx->unlock(); // 析构函数中解锁
}
}
unique_lock(const unique_lock&) = delete;
unique_lock& operator=(const unique_lock&) = delete;
void lock() { // lock the mutex
_Validate();
_Pmtx->lock();
_Owns = true;
}
_NODISCARD bool try_lock() { // try to lock the mutex
_Validate();
_Owns = _Pmtx->try_lock();
return _Owns;
}
void unlock() { // try to unlock the mutex
if (!_Pmtx || !_Owns) {
_THROW(system_error(_STD make_error_code(errc::operation_not_permitted)));
}
_Pmtx->unlock();
_Owns = false;
}
void swap(unique_lock& _Other) noexcept { // swap with _Other
_STD swap(_Pmtx, _Other._Pmtx);
_STD swap(_Owns, _Other._Owns);
}
_Mutex* release() noexcept { // 返回指向它所管理的 Mutex 对象的指针,并释放所有权
_Mutex* _Res = _Pmtx;
_Pmtx = nullptr;
_Owns = false;
return _Res;
}
_NODISCARD bool owns_lock() const noexcept { return _Owns; } // 返回当前 std::unique_lock 对象是否获得了锁
explicit operator bool() const noexcept { return _Owns; } // 返回当前 std::unique_lock 对象是否获得了锁
_NODISCARD _Mutex* mutex() const noexcept { return _Pmtx; } // return pointer to managed mutex
private:
_Mutex* _Pmtx;
bool _Owns; // 是否拥有锁(当mutex被lock时,为true;否则为false)
};
1)以独占所有权的方式管理Mutex对象的上锁和解锁操作,即没有其他的unique_lock对象同时拥有某个Mutex对象的所有权。
2)与std::lock_guard一样,在unique_lock生命期结束后,会对其所管理的Mutex进行解锁。(注意:unique_lock只对拥有所有权的mutex才会在析构函数中被自动unlock)。
3)这里再介绍下unique_lock的构造函数:
a. unique_lock()默认构造函数:新创建的unique_lock对象不管理任何Mutex对象。
b. unique_lock(_Mutex& m):构造并上锁。如果此时某个另外的unique_lock己管理m对象,则当前线程会被阻塞。
c. unique_lock(_Mutex& m, adopt_lock_t):构造,并假定m己上锁。(m需要事先被上锁,构造结束后unique_lock就拥有m的所有权)
d. unique_lock(_Mutex& _Mtx, defer_lock_t):构造,但不上锁。对象创建以后,可以手动调用unique_lock的lock来上锁,才拥有_Mtx的所有权。
强调一下,只有拥有所有权的mutex才会在析构函数中被自动unlock。
e. unique_lock(_Mutex& _Mtx, try_to_lock_t):构造,并尝试上锁。如果上锁不成功,并不会阻塞当前线程。