unique_lock功能
上一篇博文中我们介绍std::lock_guard()
互斥量函数,一般我们使用lock_guard()
取代std::mutex
的使用,为了避免忘记unlock()
情况的出现。此篇博文我们介绍一下关于std::unque_lock()
互斥量的使用方法,unique_lock()
较lock_guard()
功能更加全面一些,不只是像lock_guard()
在构造/析构函数中lock()
与unlock()
函数。同时unique_lock()
还提供多种初始化的方式std::adopt_lock
与std::defer_lock
与std::try_to_lock
多个不同参数模式。具体下面会详细介绍,我们先来看一下unique_lock
类的相关构造函数与成员函数的源码。
// CLASS TEMPLATE unique_lock
template <class _Mutex>
class unique_lock { // whizzy class with destructor that unlocks mutex
public:
using mutex_type = _Mutex;
// CONSTRUCT, ASSIGN, AND DESTROY
unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) {}
// 显示默认构造函数,构造unique_lock同时也上锁
explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock
_Pmtx->lock();
_Owns = true;
}
// 构造函数,第二个参数adopt_lock代表输入进去的互斥量_Mtx之前已经上锁了
unique_lock(_Mutex& _Mtx, adopt_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct and assume already locked
}
// 构造函数,第二个参数defer_lock代表输入进去的互斥量_Mtx没有上锁,后面会手动上锁
unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
: _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct but don't lock
}
// 构造函数,第二个参数try_to_lock代表输入进去的互斥量_Mtx没有上锁,并且尝试给_Mtx上锁
unique_lock(_Mutex& _Mtx, try_to_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) { // construct and try to lock
}
// 构造函数,第二个参数代表给_Mtx上锁需要的时间范围内要完成上锁,不然就没有锁成功
template <class _Rep, class _Period>
unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_for(_Rel_time)) { // construct and lock with timeout
}
// 构造函数,第二个函数代表在绝对某个时间点之前是否上锁成功or失败
template <class _Clock, class _Duration>
unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_until(_Abs_time)) { // construct and lock with timeout
}
unique_lock(_Mutex& _Mtx, const xtime* _Abs_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // try to lock until _Abs_time
_Owns = _Pmtx->try_lock_until(_Abs_time);
}
// 移动构造函数,获取之前的unique_lock的所有权
unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // destructive copy
_Other._Pmtx = nullptr;
_Other._Owns = false;
}
// 移动运算符,获取之前unique_lock的所有权
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;
// unique_lock类成员函数lock
void lock() { // lock the mutex
_Validate();
_Pmtx->lock();
_Owns = true;
}
// unique_lock类成员函数try_lock
_NODISCARD bool try_lock() { // try to lock the mutex
_Validate();
_Owns = _Pmtx->try_lock();
return _Owns;
}
// unique_lock类成员函数try_lock_for
template <class _Rep, class _Period>
_NODISCARD 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;
}
// unique_lock类成员函数try_lock_until
template <class _Clock, class _Duration>
_NODISCARD 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;
}
_NODISCARD 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;
}
// unique_lock类成员函数unlock
void unlock() { // try to unlock the mutex
if (!_Pmtx || !_Owns) {
_Throw_system_error(errc::operation_not_permitted);
}
_Pmtx->unlock();
_Owns = false;
}
// unique_lock类成员函数swap交换两个unique_lock所有权
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 = nullptr;
_Owns = false;
return _Res;
}
_NODISCARD 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;
}
_NODISCARD _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_system_error(errc::operation_not_permitted);
}
if (_Owns) {
_Throw_system_error(errc::resource_deadlock_would_occur);
}
}
};
上面C++11多线程源码库中的unique_lock
类的源码生命部分,其中主要分为2个功能函数划分:①构造函数的不同功能初始化;②成员函数;
- 显示默认构造函数,构造同时进行上锁,即构造函数第二个参数没有;
- 构造函数第二个参数使用
std::adopt_lock
,即该互斥量在调用unique_lock
之前已经完成上锁功能,如果没有上锁那么将会报异常; - 构造函数第二个参数使用
std::try_to_lock
,即该互斥量在调用unique_lock
时候尝试对其进行上锁,可能会上锁失败or成功; - 构造函数第二个参数使用
std::defer_lock
,即该互斥量在调用unique_lock
时候不对齐进行上锁,在后面使用时候动态的调用lock()
或者unlock()
函数进行上锁与解锁; - 模板构造函数针对在一段时间内or某一个时间点的上锁成功与否的初始化;
- 类
unique_lock
移动构造函数转移所有权;
下面是相关成员函数:
- 函数
try_lock
、try_lock_for
、try_lock_until
三个成员函数分别是对互斥量进行上锁,只是后面两个函数一个是在一段时间内是否上锁成功,try_lock_until
为绝对某个时间点是否上锁成功; - 函数
swap
为交换两个unique_lock
对象的所有权; - 函数
release
为释放unique_lock
对象的所有权;
这里对类unique_lock
源码部分的做个小结:主要针对不同的使用需求来进行不同的构造unique_lock
对象。关于构造函数的第二个参数,可根据你实际需求来进行输入。我们看到unique_lock
功能较lock_guard
类更全面,但是可能性能略微低于lock_guard
类。unique_lock
类使用更为灵活与全面,同时也避免忘记unlock
的情况。
代码演示
下面的示例代码涵盖了不同的构造函数方式,以及相关使用方法和错误使用的提示。每一小段的注释解开即可进行编译查看使用unique_lock
类的效果。相关解释使用与解释都在代码中体现,这里就不多余解释了。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block(int n, char c)
{
///
//for (int i = 0; i < n; ++i)
//{
// mtx.lock();
// std::cout << c;
// mtx.unlock();
//}
//std::cout << "\n"; // 无序输出
///
std::unique_lock<std::mutex> lck(mtx); // 有序
for (int i = 0; i < n; ++i)
{
//std::unique_lock<std::mutex> lck(mtx); // 无序
std::cout << c;
}
std::cout << "\n"; // 有序输出
///
//mtx.lock();
//std::unique_lock<std::mutex> lck(mtx, std::adopt_lock);
//for (int i = 0; i < n; ++i)
//{
// std::cout << c;
//}
//std::cout << "\n"; // 有序输出
///
//std::unique_lock<std::mutex> lck(mtx, std::try_to_lock);
//bool succ = lck.owns_lock();
//if (succ)
//{
// for (int i = 0; i < n; ++i)
// {
// std::cout << c;
// }
// std::cout << "\n";
//}
//else
//{
// std::cout << "can not lock succ." << std::endl;
//} // 竞争机制,只能输出一个线程的结果,另外一个线程打印没有lock成功
///
//std::unique_lock<std::mutex> lck(mtx, std::defer_lock);
//for (int i = 0; i < n; ++i)
//{
// lck.lock();
// std::cout << c;
// lck.unlock();
//}
//std::cout << "\n"; // 无序输出
/
//std::unique_lock<std::mutex> lck(mtx); // 有序
//std::mutex *pt_rel = lck.release(); // mtx所有权转让给pt_rel,因此需要pt_rel自己手动解锁
pt_rel->lock(); // error 接收lck.release()无需再次上锁
//for (int i = 0; i < n; ++i)
//{
// //pt_rel->lock(); // error 接收lck.release()无需再次上锁
// std::cout << c;
//}
//std::cout << "\n"; // 有序输出
//pt_rel->unlock();
///
//std::unique_lock<std::mutex> lck(mtx);
//std::unique_lock<std::mutex> lck_move(std::move(lck)); // 移动构造函数使用,转移所有权
//for (int i = 0; i < n; ++i)
//{
// std::cout << c;
//}
//std::cout << "\n";
}
int main(void)
{
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
小结
本篇博文主要介绍一下unique_lock
类的使用,其功能相比较lock_guard比较全面,灵活性较高。但是,带来一定程度的性能降低,同时复杂性有所提高。使用时候,你必须确认所使用的参数的功能注意事项。一般情况下,使用unique_lock
的频率较lock_guard
低些,如果你使用的话请注意下构造函数的相关参数类型模式即可。
参考
http://www.cplusplus.com/reference/mutex/unique_lock/