背景

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(), _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 = ;
__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 = ;
__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 = ;
_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() );
} if ( !m_queue.empty() )
{
pRtn = m_queue.front();
if ( pRtn->data != )
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\n", std::this_thread::get_id() );
printf(" %d\n", data->data );
if ( == 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=; i<THREAD_NUM; ++i )
threads[i] = std::thread( thread_fun, &cq ); int i = ;
while( i > )
{
Task *pTask = new Task( --i );
cq.PushTask( pTask );
} for ( int i=; i<THREAD_NUM; ++i)
threads[i].join(); system( "pause" );
return ;
}

在示例代码中,我们使主线程向公共队列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。

std::unique_lock与std::lock_guard分析的更多相关文章

  1. C++ 11 多线程下std::unique_lock与std::lock_guard的区别和用法

    这里主要介绍std::unique_lock与std::lock_guard的区别用法 先说简单的 一.std::lock_guard的用法 std::lock_guard其实就是简单的RAII封装, ...

  2. C++11 std::unique_lock与std::lock_guard区别及多线程应用实例

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  3. C++ 并发编程,std::unique_lock与std::lock_guard区别示例

    背景 平时看代码时,也会使用到std::lock_guard,但是std::unique_lock用的比较少.在看并发编程,这里总结一下.方便后续使用. std::unique_lock也可以提供自动 ...

  4. std::unique_lock与std::lock_guard区别示例

    std::lock_guard std::lock_guard<std::mutex> lk(frame_mutex); std::unique_lock<std::mutex> ...

  5. std::unique_lock<std::mutex> or std::lock_guard<std::mutex> C++11 区别

    http://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex The diff ...

  6. std::lock_guard/std::unique_lock

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  7. 基于std::mutex std::lock_guard std::condition_variable 和std::async实现的简单同步队列

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  8. boost::unique_lock和boost::lock_guard的区别

    lock_guard unique_lock boost::mutex mutex; boost::unique_lock<boost::mutex> lock(mutex); std:: ...

  9. std::lock_guard和std::unique_lock

    std::unique_lock也可以提供自动加锁.解锁功能,比std::lock_guard更加灵活 https://www.cnblogs.com/xudong-bupt/p/9194394.ht ...

随机推荐

  1. linux重启之后No CUDA-supporting devices found!

    实验室做并行计算的服务重启后,采用cuda接口的应用程序vasp_gpu,运行时提示: CUDA Error in cuda_main.cu, line 144: unknown error No C ...

  2. [洛谷201704R1]开心派对小火车

    OJ题号:洛谷P3697 思路: 贪心.首先从起点出发,开特急电车,对于每一个特急车站$s_{i}$,分别下一次车,计算从当前车站$s_{i}$出发坐各停电车在指定时限内$t$最远能够到达的车站$r_ ...

  3. 【概率论】1-1:概率定义(Definition of Probability)

    title: [概率论]1-1:概率定义(Definition of Probability) categories: Mathematic Probability keywords: Sample ...

  4. codeforces 1249 D2 Too Many Segments (hard version) 贪心+树状数组

    题意 给定n个线段,线段可以相交,第\(i\)个线段覆盖的区间为\([l_i,r_i]\),问最少删除多少个线段让覆盖每个点的线段数量小于等于k. 分析 从左往右扫每个点\(x\),若覆盖点\(x\) ...

  5. LOJ6436. 「PKUSC2018」神仙的游戏 [NTT]

    传送门 思路 首先通过各种手玩/找规律/严谨证明,发现当\(n-i\)为border当且仅当对于任意\(k\in[0,i)\),模\(i\)余\(k\)的位置没有同时出现0和1. 换句话说,拿出任意一 ...

  6. mac charles 代理https

    1.安装根证书:help - ssl proxying - install charles root certificate 2.这时候会弹出一个根证书界面,如果没有弹出,则可以去chrome,高级设 ...

  7. 解决Virtualbox的根分区容量不够用问题

    现在Virtualbox新建一块磁盘.容量一定要比原来的大.然后执行克隆命令. 把原来的磁盘内容克隆到新磁盘上.然后重新启动电脑. 运行相关扩容命令即可. #克隆磁盘 cd C:\Program Fi ...

  8. python3安装web.py

    今天准备测试代理池IPProxyPool获取到ip的质量,在安装web.py的时候遇到了些问题,在此记录一下. 1.安装资料 web.py官网:http://webpy.org/ web.py的git ...

  9. ROS indigo下Kinect v1的驱动安装与调试

    ROS indigo下Kinect v1的驱动安装与调试 本文简要叙述了在ROS indigo版本下Kinect v1的驱动安装与调试过程. 1. 实验环境 (1)硬件:  台式机和Kinect v1 ...

  10. 黑马vue---59-60、组件中的data和methods

    黑马vue---59-60.组件中的data和methods 一.总结 一句话总结: 1. 组件可以有自己的 data 数据 2. 组件的 data 和 实例的 data 有点不一样,实例中的 dat ...