std::mutex和lock系列

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):构造,并尝试上锁。如果上锁不成功,并不会阻塞当前线程。

 

posted @ 2020-05-31 07:53  _yanghh  阅读(1925)  评论(0编辑  收藏  举报