zoukankan      html  css  js  c++  java
  • std::unique_lock与std::lock_guard分析

    背景

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢,导致程序出现未定义或异常行为。通常的做法是在修改共享数据成员时进行加锁(mutex)。在使用锁时通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,但经常会出现lock之后离开共享成员操作区域时忘记unlock导致死锁的现象。针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次封装,实现自动unlock的功能。

    std::lock_guard

    std::lock_guard是典型的RAII实现,功能相对简单。在构造函数中进行加锁,析构函数中进行解锁。下面是std::lock_guard的源码,也非常容易看出是RAII的设计。

    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)
        {
        }
    
        ~lock_guard()
        {
            _M_device.unlock();  //析构解锁
        }
    
        lock_guard(const lock_guard &) = delete;
        lock_guard &operator=(const lock_guard &) = delete;
    
    private:
        mutex_type &_M_device;
    };

    std::unique_lock

    std::unique_lock同样能够实现自动解锁的功能,但比std::lock_guard提供了更多的成员方法,更加灵活一点,相对来说占用空也间更大并且相对较慢,即需要付出更多的时间、性能成本。下面是其源码:

    template <typename _Mutex>
    class unique_lock
    {
    public:
        typedef _Mutex mutex_type;
    
        unique_lock() noexcept
            : _M_device(0), _M_owns(false)
        {
        }
    
        explicit unique_lock(mutex_type &__m)
            : _M_device(std::__addressof(__m)), _M_owns(false)
        {
            lock();
            _M_owns = true;
        }
    
        unique_lock(mutex_type &__m, defer_lock_t) noexcept
            : _M_device(std::__addressof(__m)), _M_owns(false)
        {
        }
    
        unique_lock(mutex_type &__m, try_to_lock_t)
            : _M_device(std::__addressof(__m)), _M_owns(_M_device->try_lock())
        {
        }
    
        unique_lock(mutex_type &__m, adopt_lock_t) noexcept
            : _M_device(std::__addressof(__m)), _M_owns(true)
        {
            // XXX calling thread owns mutex
        }
    
        template <typename _Clock, typename _Duration>
        unique_lock(mutex_type &__m,
                    const chrono::time_point<_Clock, _Duration> &__atime)
            : _M_device(std::__addressof(__m)),
              _M_owns(_M_device->try_lock_until(__atime))
        {
        }
    
        template <typename _Rep, typename _Period>
        unique_lock(mutex_type &__m,
                    const chrono::duration<_Rep, _Period> &__rtime)
            : _M_device(std::__addressof(__m)),
              _M_owns(_M_device->try_lock_for(__rtime))
        {
        }
    
        ~unique_lock()
        {
            if (_M_owns)
                unlock();
        }
    
        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;
        }
    
        void
        lock()
        {
            if (!_M_device)
                __throw_system_error(int(errc::operation_not_permitted));
            else if (_M_owns)
                __throw_system_error(int(errc::resource_deadlock_would_occur));
            else
            {
                _M_device->lock();
                _M_owns = true;
            }
        }
    
        bool
        try_lock()
        {
            if (!_M_device)
                __throw_system_error(int(errc::operation_not_permitted));
            else if (_M_owns)
                __throw_system_error(int(errc::resource_deadlock_would_occur));
            else
            {
                _M_owns = _M_device->try_lock();
                return _M_owns;
            }
        }
    
        template <typename _Clock, typename _Duration>
        bool
        try_lock_until(const chrono::time_point<_Clock, _Duration> &__atime)
        {
            if (!_M_device)
                __throw_system_error(int(errc::operation_not_permitted));
            else if (_M_owns)
                __throw_system_error(int(errc::resource_deadlock_would_occur));
            else
            {
                _M_owns = _M_device->try_lock_until(__atime);
                return _M_owns;
            }
        }
    
        template <typename _Rep, typename _Period>
        bool
        try_lock_for(const chrono::duration<_Rep, _Period> &__rtime)
        {
            if (!_M_device)
                __throw_system_error(int(errc::operation_not_permitted));
            else if (_M_owns)
                __throw_system_error(int(errc::resource_deadlock_would_occur));
            else
            {
                _M_owns = _M_device->try_lock_for(__rtime);
                return _M_owns;
            }
        }
    
        void
        unlock()
        {
            if (!_M_owns)
                __throw_system_error(int(errc::operation_not_permitted));
            else if (_M_device)
            {
                _M_device->unlock();
                _M_owns = false;
            }
        }
    
        void
        swap(unique_lock &__u) noexcept
        {
            std::swap(_M_device, __u._M_device);
            std::swap(_M_owns, __u._M_owns);
        }
    
        mutex_type *
        release() noexcept
        {
            mutex_type *__ret = _M_device;
            _M_device = 0;
            _M_owns = false;
            return __ret;
        }
    
        bool
        owns_lock() const noexcept
        {
            return _M_owns;
        }
    
        explicit operator bool() const noexcept
        {
            return owns_lock();
        }
    
        mutex_type *
        mutex() const noexcept
        {
            return _M_device;
        }
    
    private:
        mutex_type *_M_device;
        bool _M_owns; // XXX use atomic_bool
    };
    
    template <typename _Mutex>
    inline void
    swap(unique_lock<_Mutex> &__x, unique_lock<_Mutex> &__y) noexcept
    {
        __x.swap(__y);
    }

    从上面的源码对比非常容易看出std::unique_lock的实现比std::lock_guard复杂多了,提供了几个方法使编程更灵活,具体如下:

    lock locks the associated mutex 
    try_lock tries to lock the associated mutex, returns if the mutex is not available 
    try_lock_for attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration 
    try_lock_until tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached 
    unlock unlocks the associated mutex 

    以上方法,可以通过lock/unlock可以比较灵活的控制锁的范围,减小锁的粒度。通过try_lock_for/try_lock_until则可以控制加锁的等待时间,此时这种锁为乐观锁。

    std::unique_lock与条件变量

    这里举个并发消息队列的简单例子,是std::unique_lock与条件变量配合使用经典场景,并发消费共享成员变量m_queue的内容,且保证线程安全。

    #include <queue>
    #include <mutex>
    #include <thread>
    #include <chrono>
    #include <memory>
    #include <condition_variable>
    
    typedef struct task_tag
    {
        int data;
        task_tag( int i ) : data(i) { }
    } Task, *PTask;
    
    class MessageQueue
    {
    public:
        MessageQueue(){}
        ~MessageQueue()
        {
            if ( !m_queue.empty() )
            {
                PTask pRtn = m_queue.front();
                delete pRtn;
            }
            
        }
    
        void PushTask( PTask pTask )
        {
            std::unique_lock<std::mutex> lock( m_queueMutex );
            m_queue.push( pTask );
            m_cond.notify_one();
        }
    
        PTask PopTask()
        {
            PTask pRtn = NULL;
            std::unique_lock<std::mutex> lock( m_queueMutex );
            while ( m_queue.empty() )
            {
                m_cond.wait_for( lock, std::chrono::seconds(1) );
            }
    
            if ( !m_queue.empty() )
            {
                pRtn = m_queue.front();
                if ( pRtn->data != 0 )
                    m_queue.pop();
            }
    
            return pRtn;
        }
    
    private:
        std::mutex m_queueMutex;
        std::condition_variable m_cond; 
        std::queue<PTask> m_queue;
    };
    
    void thread_fun( MessageQueue *arguments )
    {
        while ( true )
        {
            PTask data = arguments->PopTask();
    
            if (data != NULL)
            {
                printf( "Thread is: %d
    ", std::this_thread::get_id() );
                printf("   %d
    ", data->data );
                if ( 0 == data->data ) //Thread end.
                    break;
                else
                    delete data;
            }
        }
    }
    
     int main( int argc, char *argv[] )
    {
        MessageQueue cq;
    
        #define THREAD_NUM 3
        std::thread threads[THREAD_NUM];
    
        for ( int i=0; i<THREAD_NUM; ++i )
            threads[i] = std::thread( thread_fun, &cq );
    
        int i = 10;
        while( i > 0 )
        {
            Task *pTask = new Task( --i );
            cq.PushTask( pTask );
        }
    
        for ( int i=0; i<THREAD_NUM; ++i) 
            threads[i].join();
    
        system( "pause" );
        return 0;
    }

    在示例代码中,我们使主线程向公共队列cq中Push任务,而其他的线程则负责取出任务并打印任务,由于std::cout并不支持并发线程安全,所以在打印任务时使用printf。主线程new出的任务,在其他线程中使用并销毁,当主线程发送data为0的任务时,则规定任务发送完毕,而其他的线程获取到data为0的任务后退出线程,data为0的任务则有消息队列负责销毁。整个消息队列使用标准模板库实现,现实跨平台。

    std::unique_lock与std::lock_guard区别

    上述例子中,std::unique_lock在线程等待期间解锁mutex,并在唤醒时重新将其锁定,而std::lock_guard却不具备这样的功能。所以std::unique_lock和std::lock_guard在编程应用中的主要区别总结如下:

    • 如果只为保证数据同步,那么std::lock_guard完全够用;
    • 如果除了同步还需要实现条件阻塞时,那么就需要用std::unique_lock。
  • 相关阅读:
    JS获当前网页元素高度offsetHeight
    C-LODOP回调多个返回值On_Return_Remain
    JS的slice、substring、substr字符串截取
    【JS新手教程】JS修改css样式的几种方法
    Unity GUI(uGUI)使用心得与性能总结
    PDB文件:每个开发人员都必须知道的
    IEnumerable 使用foreach 详解
    Unity------Unity 脚本基类 MonoBehaviour 与 GameObject 的关系
    Unity5-----------之GI设置简介
    unity5x --------Music Mixer参数详解
  • 原文地址:https://www.cnblogs.com/evenleee/p/11854619.html
Copyright © 2011-2022 走看看