C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock
Posted 木大白易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock相关的知识,希望对你有一定的参考价值。
简介
C++11中新增了std::mutex
,它是C++标准程序库中的一个头文件,定义了C++11标准中的一些互斥访问的类与方法等。
mutex
是mutual exclusion(互斥)的简写。
共有4个类可以使用:
API | 说明 |
---|---|
mutex | 提供基本互斥设施 |
timed_mutex | 提供互斥设施,带有超时功能 |
recursive_mutex | 提供能被同一线程递归锁定的互斥设施 |
recursive_timed_mutex | 提供能被同一线程递归锁定的互斥设施,带有超时功能 |
mutex的函数成员
访问共享数据的代码片段称之为临界区(critical section)。
我们现在先只关注最基本的mutex
,mutex
是最基础的API。其他类都是在它的基础上的改进。所以这些类都提供了下面三个方法,并且它们的功能是一样的:
方法 | 说明 |
---|---|
lock() | 锁定互斥体,如果不可用,则阻塞 |
try_lock() | 尝试锁定互斥体,如果不可用,直接返回 |
unlock () | 解锁互斥体 |
这三个方法提供了基础的锁定和解除锁定的功能。使用lock意味着你有很强的意愿一定要获取到互斥体,而使用try_lock则是进行一次尝试。这意味着如果失败了,你通常还有其他的路径可以走。
所以最基本的使用:
std::mutex mtx;
void someOp()
mtx.lock(); //加锁
... //执行你的操作,这块是临界区
mtx.unlock(); //解锁
在进入临界区时,执行lock()加锁操作,如果这时已经被其它线程锁住,则当前线程在此排队等待。退出临界区时,执行unlock()解锁操作。
通用互斥管理
互斥体(mutex相关类)提供了对于资源的保护功能,但是手动的锁定(调用lock或者try_lock)和解锁(调用unlock)互斥体是要耗费比较大的精力的。因为我们需要保证,在任何情况下,解锁要和加锁配对,因为假设出现一条路径导致获取锁之后没有正常释放(比如在临界区中因为抛出异常或return等操作导致没有解锁就退出的问题),就会影响整个系统。如果考虑方法还可以会抛出异常,这样的代码写起来会很费劲。
更好的办法是采用“资源分配时初始化(RAII)”方法来加锁、解锁,标准库就提供了下面的这些API,来简化我们手动加锁和解锁的“体力活”。
API | 说明 |
---|---|
lock_guard | 实现严格基于作用域的互斥体所有权包装器 |
unique_lock | 实现可移动的互斥体所有权包装器 |
锁定策略 | 说明 |
---|---|
defer_lock | 类型为 defer_lock_t,不获得互斥的所有权 |
try_to_lock | 类型为try_to_lock_t,尝试获得互斥的所有权而不阻塞 |
adopt_lock | 类型为adopt_lock_t,假设调用方已拥有互斥的所有权 |
lock_guard
所以上边的代码可以变成这样:
std::mutex mtx;
void someOp()
std::lock_guard<std::mutex> lock(mtx);; //加锁
... //执行你的操作,这块是临界区
//出了作用域,自动释放锁mtx
在std::lock_guard
对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard
对象被析构时,它所管理的mutex对象会自动解锁,不需要程序员手动调用lock和unlock对mutex进行上锁和解锁操作。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。程序员可以非常方便地使用lock_guard,而不用担心异常安全问题。
unique_lock
unique_lock具有lock_guard的全部功能,但是更加灵活:
- lock_guard在构造时或者构造前(std::adopt_lock)就已经获取互斥锁,并且在作用域内保持获取锁的状态,直到作用域结束;而unique_lock在构造时或者构造后(std::defer_lock)获取锁,在作用域范围内可以手动获取锁和释放锁,作用域结束时如果已经获取锁则自动释放锁。
- lock_guard锁的持有只能在lock_guard对象的作用域范围内,作用域范围之外锁被释放,而unique_lock对象支持移动操作,可以将unique_lock对象通过函数返回值返回,这样锁就转移到外部unique_lock对象中,延长锁的持有时间。
所以上边代码也可以这样:
std::mutex mtx;
void someOp()
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);; //此时还未加锁
lock.lock();//手动获取锁
... //执行你的操作,这块是临界区
lock.unlock();//手动释放锁
另外和条件变量condition_variable
一起使用的时候,也需要用unique_lock
!
推荐阅读
以上是关于C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock的主要内容,如果未能解决你的问题,请参考以下文章