《探索C++多线程》:mutex源码(二)
来源:互联网 发布:jquery.mloading.js 编辑:程序博客网 时间:2024/06/06 05:03
通过前面三节,我们学习了线程、互斥量,乘热打铁,在这一节中我们来学习关于锁(lock)的相关知识,关于锁的声明和定义,也在头文件<mutex>中。
锁lock的类型
lock_guardC++11区域锁unique_lockC++11区域锁,提供了更加灵活的操作shared_lockC++14提供共享的互斥量的锁操作scoped_lockC++17提供多个互斥量时避免死锁RAII的锁操作很多时候我们在编写多线程代码时,在对互斥量加锁lock()后,有时可能会忘记解锁unlock(),这样就会导致该线程一直持有互斥量,而其他与其存在资源竞争的线程将会一直获取不到所有权。这完全就违背我我们利用多线程处理任务愿望。
关于lock_guard
在了解lock_guard之前,我们先来看一段代码:
#include <iostream>#include <thread>#include <mutex>using namespace std;volatile int cnt = 0;mutex mtx;void task() { for (int i = 0; i < 10; i++) { lock_guard<mutex> lck(mtx); // 使用lock_guard cout << this_thread::get_id() << "-----" << cnt++ << endl; }}int main() { thread t[3]; for (int i = 0; i < 3; i++) { t[i] = thread(task); } for (int i = 0; i < 3; i++) { t[i].join(); } return 0;}看完上面的代码,可能大家会有疑问,虽然有互斥量mtx,但是并没有调用mtx对象方法:mtx.lock()或者mtx.try_lock(),也没有 出现mtx.unlock()啊。程序是怎么做到加锁、解锁的呢?
其实这就是 lock_guard<mutex> lck(mtx) 这句代码起到的功效了。好的,现在我们带着疑问来解读一下lock_guard了。
其定义如下:
template<class _Mutex>class lock_guard { // 利用析构函数解锁互斥量public:typedef _Mutex mutex_type;explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // ① 构造,并加锁_MyMutex.lock();}lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { } // ② 构造,但不加锁~lock_guard() _NOEXCEPT {// 析构函数,解锁_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;private:_Mutex& _MyMutex;};由上述定义可以知道,这是一个类模板,结构很简单,该类中定义了一个引用变量,还提供了构造函数、析构函数,禁用了拷贝构造函数和赋值函数。
lock_guard<mutex> lck(mtx);我们再来看上面这句代码做了些什么事?显然使用的构造函数①,在初始化列表中初始化了互斥量,在函数体中对互斥量进行上锁,因此实例化一个对象的时候,便已经上锁了。
那么什么时候解锁呢?我们看到在析构函数中,对互斥量进行了解锁。也就是说,如果要解锁,那么就必须调用析构函数;我们回到上面之前的代码中分析,在for循环中,每结束一次循环,便会自动将lck析构掉,因此lock_guard的工作原理便很清晰明了了。
总的来说:lock_guard本质就是,利用程序块结束,对象被析构时进行自动解锁。这样一来,我们只要将需要进行加锁的操作(设计资源共享的代码部分)与实例化lock_guard对象放在一个语句块中即可。如:
volatile int cnt = 0;mutex mtx;void task() { for (int i = 0; i < 1000; i++) { { // begin lock_guard<mutex> lck(mtx); cnt++; if (cnt > 1000) cnt = 1000; else if (cnt < 0) cnt = 0; } // end }}另外,lock_guard类中还有一个方法,在源码的注释中讲到了,该方法也是实例化一个对象,但不对互斥量上锁。主要是为了避免互斥量在已经被上锁的情况下再上锁,用法如下:
volatile int cnt = 0;mutex mtx;void task() { for (int i = 0; i < 1000; i++) { { // begin mtx.lock(); // 已上锁 lock_guard<mutex> lck(mtx, adopt_lock); cnt++; if (cnt > 1000) cnt = 1000; else if (cnt < 0) cnt = 0; } // end }}其中adopt_lock是一个空类(结构体),相信读过侯JJ《STL源码剖析》的都知道(用一个空类或结构体来进行方法的重载的用法),这里也是同样的用法。
其声明与定义如下:
struct adopt_lock_t { };struct defer_lock_t { };struct try_to_lock_t { };extern _CRTIMP2_PURE const adopt_lock_t adopt_lock;extern _CRTIMP2_PURE const defer_lock_t defer_lock;extern _CRTIMP2_PURE const try_to_lock_t try_to_lock;好了,以上就是对lock_guard的解析、用途和使用用法。
关于unique_lock
lock_guard锁非常的简单和方便,但它实在是太简洁了,而unique_lock锁为程序员提供更为灵活、强大的互斥操作、管理方法,既然是要强大一下,自然其定义就会复杂,更长一些,如下:
template<class _Mutex>class unique_lock {public:typedef unique_lock<_Mutex> _Myt;typedef _Mutex mutex_type; // --------------------------------------构造,赋值,销毁---------------------------------------------unique_lock() _NOEXCEPT : _Pmtx(0), _Owns(false) { } // ① 构造,空explicit unique_lock(_Mutex& _Mtx) : _Pmtx(&_Mtx), _Owns(false) { // ② 构造,并上锁_Pmtx->lock();_Owns = true;}unique_lock(_Mutex& _Mtx, adopt_lock_t) : _Pmtx(&_Mtx), _Owns(true) { } // ③ 构造,假设已经上锁unique_lock(_Mutex& _Mtx, defer_lock_t) _NOEXCEPT : _Pmtx(&_Mtx), _Owns(false) { } // ④ 构造,不上锁unique_lock(_Mutex& _Mtx, try_to_lock_t) : _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock()) { } // ⑤ 构造,尝试上锁 // ⑥ 构造,在_Rel_time时间内尝试上锁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)) { }// ⑦ 构造,在_Rel_time时间内尝试上锁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)) { }unique_lock(_Mutex& _Mtx, const xtime *_Abs_time) : _Pmtx(&_Mtx), _Owns(false) { // ⑧ 构造,在_Abs_time时间点到来之前尝试上锁_Owns = _Pmtx->try_lock_until(_Abs_time);}unique_lock(unique_lock&& _Other) _NOEXCEPT : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) {// ⑨ 构造,使用move赋值_Other._Pmtx = 0;_Other._Owns = false;} // ----------------------------------------加锁,解锁-------------------------------------------unique_lock& operator=(unique_lock&& _Other) _NOEXCEPT {// 赋值函数,使用move赋值if (this != &_Other) { // _Other与自身对象不同(即:不是自己赋值给自己)if (_Owns) // 已锁定_Pmtx->unlock(); // 解锁_Pmtx = _Other._Pmtx;_Owns = _Other._Owns;_Other._Pmtx = 0;_Other._Owns = false;}return (*this);}~unique_lock() _NOEXCEPT { // 析构if (_Owns) // 若互斥量在析构之前已经解锁了,由于存在_Owns标志,所以不会发生析构异常_Pmtx->unlock(); // 若没有,则正常析构}unique_lock(const unique_lock&) = delete; // 禁用拷贝构造函数unique_lock& operator=(const unique_lock&) = delete; // 禁用复制函数void lock() { // 上锁_Pmtx->lock();_Owns = true;}bool try_lock() _NOEXCEPT { // 尝试上锁_Owns = _Pmtx->try_lock();return (_Owns);}template<class _Rep, class _Period>bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // 在一段时间内(_Rel_time)尝试上锁_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) {// 尝试上锁,直到绝对时间点(_Abs_time)到来_Owns = _Pmtx->try_lock_until(_Abs_time);return (_Owns);}bool try_lock_until(const xtime *_Abs_time) { // 尝试上锁,直到绝对时间点(_Abs_time)到来_Owns = _Pmtx->try_lock_until(_Abs_time);return (_Owns);}void unlock() { // 解锁_Pmtx->unlock();_Owns = false;} // ----------------------------------------变换-------------------------------------------void swap(unique_lock& _Other) _NOEXCEPT { //交换量unique_lock_STD swap(_Pmtx, _Other._Pmtx);_STD swap(_Owns, _Other._Owns);}_Mutex *release() _NOEXCEPT { // 断开与对象的关联(注意,调用该方法后互斥量并未解锁,所以要记得在合适的时候解锁哦)_Mutex *_Res = _Pmtx;_Pmtx = 0;_Owns = false;return (_Res); //返回unique_lock对象管理的互斥量对象的指针(下一步要解锁的话,可以用_Res->unlock())} // ----------------------------------------查看-------------------------------------------bool owns_lock() const _NOEXCEPT { // 若对象持有锁,则返回truereturn (_Owns);}explicit operator bool() const _NOEXCEPT { // 若对象持有锁,则返回truereturn (_Owns);}_Mutex *mutex() const _NOEXCEPT { // 返回互斥量的指针return (_Pmtx);}private:_Mutex *_Pmtx; // 互斥量bool _Owns; // 上锁标识};
代码是有一点点长,光是构造函数就有9个(⊙o⊙)…闷一口气,我们接下来动手开始分析。
先来看是私有成员变量:一个是互斥量_Mutex,一个是标志_Owns(此标志用来表示互斥量有没有被上锁:若上锁,则为true;否则,为false)。
构造函数
① 构造一个空的unique_lock对象;
② 使用互斥量构造unique_lock对象,并对互斥量上锁(_Owns = ture);
③ 使用已上锁的互斥量构造unique_lock对象(_Owns = ture);
④ 使用互斥量构造unique_lock对象,但不对互斥量上锁(_Owns = false);
⑤ 使用互斥量构造unique_lock对象,并尝试对互斥量上锁(_Owns = _Pmtx->try_lock());
⑥ 使用互斥量构造unique_lock对象,并在_Rel_time时间内尝试对互斥量上锁(_Owns = _Pmtx->try_lock_for(_Rel_time));
⑦ 使用互斥量构造unique_lock对象,并在_Abs_time时间内尝试对互斥量上锁(_Owns = _Pmtx->try_lock_until(_Abs_time));
⑧ 使用互斥量构造unique_lock对象,并在_Abs_time时间点到来之前尝试对互斥量上锁(_Owns = _Pmtx->try_lock_until(_Abs_time));
⑨ 使用通过move(_Mutex)得到的互斥量构造unique_lock对象;
通过构造函数,我们有了大致的了解。接下来我们继续分析unique_lock是如何加上锁与解锁的。
加锁与解锁
加锁:构造函数、显式的调用lock()、try_lock()、try_lock_for()、try_lock_until()方法,这样就可以对互斥量进行加锁了;
解锁:显式的调用析构函数、调用unlock()方法;
unique_lock的变换
1、swap() —— 交换量unique_lock:_Pmtx,_Owns都需要交换(如定义);
2、release() —— 断开unique_lock对象与互斥量的关联(返回unique_lock管理的互斥量的指针,将_Owns 置0,注意此时并未解锁);
其他方法
1、unique_lock::owns_lock():判断unique_lock对象是否持有锁;
2、unique_lock::operator:判断unique_lock对象是否持有锁;
3、unique_lock::mutex():返回unique_lock对象管理的互斥量的指针;
以上便是unique_lock的主要特性,灵活使用unique_lock会给程序上带来很大的便利性。
补充
最后再补充一下之前漏掉的几个空类(结构体)的意义,这里再摘抄一遍:
struct adopt_lock_t { };struct defer_lock_t { };struct try_to_lock_t { };extern _CRTIMP2_PURE const adopt_lock_t adopt_lock;extern _CRTIMP2_PURE const defer_lock_t defer_lock;extern _CRTIMP2_PURE const try_to_lock_t try_to_lock;
adopt_lock_t(对应adopt_lock)
作为lock_guard或unique_lock构造参数的值。adopt_lock作为参数构造锁(lock_guard或unique_lock)对象时,假设互斥量已经由当前线程加锁了,所以不会对互斥量进行上锁。
defer_lock_t(对应defer_lock)
作为lock_guard或unique_lock构造参数的值。defer_lock作为参数构造锁(lock_guard或unique_lock)对象时,不加锁也不持有锁。
try_to_lock_t(对应try_to_lock)
作为lock_guard或unique_lock构造参数的值。try_to_lock作为参数构造锁(lock_guard或unique_lock)对象时,会调用互斥量的try_lock()方法进行加锁。
若有理解失误或表达不清的地方,还请大家多多指教,谢谢~
- 《探索C++多线程》:mutex源码(二)
- 《探索C++多线程》:mutex源码(一)
- 《探索C++多线程》:thread源码(二)
- 《探索C++多线程》:condition_variable源码(二)
- 《探索C++多线程》:future源码(二)
- 多线程(C++)同步Mutex
- 多线程(C++)同步Mutex
- C++——多线程编程(二)std::mutex 线程同步、解决资源竞争问题
- netty源码探索(二)
- 多线程互斥--mutex(二)
- C#多线程同步(二)【Mutex】
- 《探索C++多线程》:thread源码(一)
- 《探索C++多线程》:condition_variable源码(一)
- 《探索C++多线程》:future源码(一)
- 深入浅出Mutex(二)
- C#:多线程编程探索
- linux多线程mutex (一)
- c# 多线程 --Mutex(互斥锁)
- zTree插件加载目录的处理
- [CoffeeBot] 等待挑战
- [COGS 1487]麻球繁衍:概率
- ORA-29273: HTTP 请求失败; ORA-06512: 在 "SYS.UTL_HTTP", line 1722 ;ORA-24247:网络访问被访问控制列表(ACL)拒绝; ORA-06512: 在 line 1
- CentOS 7 优化
- 《探索C++多线程》:mutex源码(二)
- Linux下Opencv的安装及配置使用
- 【论文党福利】如何提取图像中的数据
- 使用CSS3中的box-flex功能实现垂直等高、水平均分、比例划分布局
- 机器学习笔记之SVM(SVR)算法
- 交叉编译器arm-linux-gcc缺少libstdc++.so.6
- C++学习笔记之——局部对象和临时对象的构造和析构时机
- 升级Xcode到8.3.1报error: Invalid bitcode signature错误
- C语言中配置文件解析案例