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

  • 相关阅读:
    linux学习之高并发服务器篇(二)
    linux学习之多高并发服务器篇(一)
    Linux学习之socket编程(二)
    Linux学习之socket编程(一)
    CMSIS-RTOS功能概述
    CMSIS-RTOS的使用
    CMSIS-RTOS 简介
    深入理解void以及void指针的含义
    C语言操作数截断
    惊人发现:火星上有水!会有生命吗? 未完
  • 原文地址:https://www.cnblogs.com/yanghh/p/12996325.html
Copyright © 2020-2023  润新知