美文网首页
C++并发编程 - 互斥锁(lock_guard和unique_

C++并发编程 - 互斥锁(lock_guard和unique_

作者: 拂去尘世尘 | 来源:发表于2022-07-24 18:47 被阅读0次

    C++并发编程 - 互斥锁

    在多线程的编程中,共享数据的修改限制是必不可少的环节。期望的是:当一个线程访问共享数据期间,此数据不应该被其他线程修改;当某个线程修改了共享数据,应通知其他线程。

    例如,买车票场景: 座位为共享数据,每个用户属于一个访问共享数据的线程,当一个用户开始购买某个座位车票期间,该座位就应该禁止被其他用户购买。从而避免同一个座位同时被两个用户买到。

    通常情况下,解决类似并发问题,首先考虑舍弃并发;若迫不得已,互斥量(mutex)是一个很好选择。

    互斥锁

    互斥量
    互斥锁是依赖互斥量实现的。互斥量可简单理解为仅有两种值true或false的信号量。

    互斥锁

    互斥锁基于互斥量实现,可用于共享数据访问的保护。即当线程访问共享数据时,有如下动作:

    • 访问前,判断互斥锁是否已上锁(互斥量是否置为true)。若上锁,说明有其他线程再访问,当前线程阻塞直至互斥锁解锁;若未上锁,当前线程上锁,并访问共享数据。
    • 访问后,退出共享数据的访问,并解锁互斥锁。

    在Linux C中互斥锁pthread_mutex_t方法,但是对于C++编程中,更推荐使用lock_guard、unqiue_lock。主要有以下优势:

    • 无需考虑互斥量的初始化和销毁,在类的构造和析构函数中管理,无需使用者操心。

    • 采用RAII对互斥量进行了不同封装,提供了更方便的上锁机制。

    对比pthread_mutex_t,功能都一样,只是使用上更加方便和灵活。毕竟经过c++大佬们深思熟虑设计出来的,如果没有优势,也就不会发布出来。

    lock_guard

    lock_guard功能与std::mutex的lock与ublock功能相同。 不同的是,lock_guard析构时会自动解锁,使用时无须unlock。这就需要我们将共享资源的访问封装成尽可能小的函数,避免加锁时间过长。

    lock_guard类主要源码

    template<class _Mutex>
    class lock_guard    
    {   
    public:
        using mutex_type = _Mutex;
    
        // construct and lock
        explicit lock_guard(_Mutex& _Mtx)
            : _MyMutex(_Mtx)
        {
            _MyMutex.lock();
        }
    
        // construct but don't lock
        lock_guard(_Mutex& _Mtx, adopt_lock_t)
            : _MyMutex(_Mtx)
        {   
        }
    
        // destructor and unlocks
        ~lock_guard() noexcept
        {
            _MyMutex.unlock();
        }
    
        lock_guard(const lock_guard&) = delete;
        lock_guard& operator=(const lock_guard&) = delete;
    
    private:
        _Mutex& _MyMutex;
    };
    

    从构造与析构可以看出,lock_guard对象创建时会主动调用lock()加锁,销毁时会主动调用unlock()解锁。

    unique_lock

    unique_lock比lock_guard更加灵活,但性能不如lock_guard。unique_lock提供lock与unlock,同时析构时也会释放锁。

    std::unique_lock 可以在构造时传递第二个参数用于管理互斥量,且能传递不同域中互斥量所有权。

    unique_lock类主要源码

    template<class _Mutex>
    class unique_lock
    {   // whizzy class with destructor that unlocks mutex
    public:
        typedef unique_lock<_Mutex> _Myt;
        typedef _Mutex mutex_type;
     
        // CONSTRUCT, ASSIGN, AND DESTROY
        unique_lock() _NOEXCEPT
            : _Pmtx(0), _Owns(false)
        {   // default construct
        }
     
        explicit unique_lock(_Mutex& _Mtx)
            : _Pmtx(&_Mtx), _Owns(false)
        {   // construct and lock
            _Pmtx->lock();
            _Owns = true;
        }
     
        unique_lock(_Mutex& _Mtx, adopt_lock_t)
            : _Pmtx(&_Mtx), _Owns(true)
        {   // construct and assume already locked
        }
     
        unique_lock(_Mutex& _Mtx, defer_lock_t) _NOEXCEPT
            : _Pmtx(&_Mtx), _Owns(false)
        {   // construct but don't lock
        }
     
        unique_lock(_Mutex& _Mtx, try_to_lock_t)
            : _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock())
        {   // construct and try to lock
        }
     
        template<class _Rep,
            class _Period>
            unique_lock(_Mutex& _Mtx,
                const chrono::duration<_Rep, _Period>& _Rel_time)
            : _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock_for(_Rel_time))
        {   // construct and lock with timeout
        }
     
        template<class _Clock,
            class _Duration>
            unique_lock(_Mutex& _Mtx,
                const chrono::time_point<_Clock, _Duration>& _Abs_time)
            : _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock_until(_Abs_time))
        {   // construct and lock with timeout
        }
     
        unique_lock(_Mutex& _Mtx, const xtime *_Abs_time)
            : _Pmtx(&_Mtx), _Owns(false)
        {   // try to lock until _Abs_time
            _Owns = _Pmtx->try_lock_until(_Abs_time);
        }
     
        unique_lock(unique_lock&& _Other) _NOEXCEPT
            : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns)
        {   // destructive copy
            _Other._Pmtx = 0;
            _Other._Owns = false;
        }
     
        unique_lock& operator=(unique_lock&& _Other)
        {   // destructive copy
            if (this != &_Other)
            {   // different, move contents
                if (_Owns)
                    _Pmtx->unlock();
                _Pmtx = _Other._Pmtx;
                _Owns = _Other._Owns;
                _Other._Pmtx = 0;
                _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;
     
        // LOCK AND UNLOCK
        void lock()
        {   // lock the mutex
            _Validate();
            _Pmtx->lock();
            _Owns = true;
        }
     
        bool try_lock()
        {   // try to lock the mutex
            _Validate();
            _Owns = _Pmtx->try_lock();
            return (_Owns);
        }
     
        template<class _Rep,
            class _Period>
            bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time)
        {   // try to lock mutex for _Rel_time
            _Validate();
            _Owns = _Pmtx->try_lock_for(_Rel_time);
            return (_Owns);
        }
     
        template<class _Clock,
            class _Duration>
            bool try_lock_until(
                const chrono::time_point<_Clock, _Duration>& _Abs_time)
        {   // try to lock mutex until _Abs_time
            _Validate();
            _Owns = _Pmtx->try_lock_until(_Abs_time);
            return (_Owns);
        }
     
        bool try_lock_until(const xtime *_Abs_time)
        {   // try to lock the mutex until _Abs_time
            _Validate();
            _Owns = _Pmtx->try_lock_until(_Abs_time);
            return (_Owns);
        }
     
        void unlock()
        {   // try to unlock the mutex
            if (!_Pmtx || !_Owns)
                _THROW_NCEE(system_error,
                    _STD make_error_code(errc::operation_not_permitted));
     
            _Pmtx->unlock();
            _Owns = false;
        }
     
        // MUTATE
        void swap(unique_lock& _Other) _NOEXCEPT
        {   // swap with _Other
            _STD swap(_Pmtx, _Other._Pmtx);
            _STD swap(_Owns, _Other._Owns);
        }
     
        _Mutex *release() _NOEXCEPT
        {   // disconnect
            _Mutex *_Res = _Pmtx;
            _Pmtx = 0;
            _Owns = false;
            return (_Res);
        }
     
        // OBSERVE
        bool owns_lock() const _NOEXCEPT
        {   // return true if this object owns the lock
            return (_Owns);
        }
     
        explicit operator bool() const _NOEXCEPT
        {   // return true if this object owns the lock
            return (_Owns);
        }
     
        _Mutex *mutex() const _NOEXCEPT
        {   // return pointer to managed mutex
            return (_Pmtx);
        }
     
    private:
        _Mutex *_Pmtx;
        bool _Owns;
     
        void _Validate() const
        {   // check if the mutex can be locked
            if (!_Pmtx)
                _THROW_NCEE(system_error,
                    _STD make_error_code(errc::operation_not_permitted));
     
            if (_Owns)
                _THROW_NCEE(system_error,
                    _STD make_error_code(errc::resource_deadlock_would_occur));
        }
    };
     
    // SWAP
    template<class _Mutex>
        void swap(unique_lock<_Mutex>& _Left,
            unique_lock<_Mutex>& _Right) _NOEXCEPT
    {   // swap _Left and _Right
        _Left.swap(_Right);
    }
    

    unique_lock私有成员为指针 _Mutex *_Pmtx,指向传递进来的互斥量,lock_guard私有成员为引用_Mutex& _MyMutex,引用传递进的互斥量。这就决定了unique_lock能够实现传递互斥量的功能。

    另外通过观察unique_lock几种构造,不同的情况可使用对应的构造创建对象:

    • unique_lock(mutex)
      传递未被使用的mutex,通过。会上锁,无法获得锁时会阻塞。

    • unique_lock(mutex, adopt_lock_t)
      传递被使用过的mutex,且已经被上过锁,通过。无上锁动作,不阻塞。

    • unique_lock(mutex, defer_lock_t)
      传递被使用过的mutex,未被上过锁。无上锁动作,不阻塞。

    • unique_lock(mutex, try_to_lock_t)
      任何状态的mutex。尝试上锁,不阻塞。

    • unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time)
      在指定时间长内尝试获取传递的mutex的锁返回。若无法获取锁,会阻塞到指定时间长。

    • unique_lock(mutex_type& m,std::chrono::time_point<Clock,Duration> const& absolute_time)
      在给定时间点尝试获取传递的mutex锁返回。若无法获取锁,会阻塞到指定时间点。

    • unique_lock(unique_lock&& _Other)
      将已经创建的unique_lock锁的所有权转移到新的锁。保持之前锁的状态,不阻塞。

    unique_lock的用法比较多,如果对锁的需求比较简单推荐使用lock_guard。当需要超时或者手动解锁等功能,可以考虑使用unique_lock

    总结

    • 相对于Linux原生互斥锁的API,C++封装的lock_guardunique_lock使用更方便和灵活。如果不是有执念,可以尝试使用C++的接口。

    • lock_guard与unique_lock的差异主要在于对mutex的管理,其根本取决于两者对于mutex的存储方式不同。lock_guard通过内部成员变量存储mutex,故其无法操作原本的mutex。而unique_lock通过内部指针指向mutex,故其能够操作和传递原本的mutex。

    相关文章

      网友评论

          本文标题:C++并发编程 - 互斥锁(lock_guard和unique_

          本文链接:https://www.haomeiwen.com/subject/zqlwirtx.html