C++11: Mutex和Lock
2016-03-18 00:00
477 查看
Mutex全名mutual exclusion(互斥体),是一个可加锁对象(lockable object),用来协助采取独占排他方式控制对资源的并发访问.这里的资源可能是个object,也可能是多个object的组合。为了获得独占式的资源访问能力,相应的线程必须锁定(lock)mutex,这样可以防止其他线程也锁定mutex,直到第一个解锁mutex的线程.
-----------------------------我是分割线-------------------------------------
简单使用Mutex和Lock:
假设我们有 int val 我们想在并发访问(concurrent access)中保护该 val,下面是一个比较粗浅的做法.
以后每次访问 val 都必须锁住 valMutex 以求独占.下面是一个很不粗糙的做法:
从上面我们可以看出来,凡是可能发生并发访问(concurrent access)的地方都需要使用同一个std::mutex,不论是读写都是如此.但是上面也随之带来了问题,当上面的某个线程throw exception的时候,就会造成资源(比如上面例子中的 val 永远被锁住)。
C++标准库提供了更好的解决方案:
上面的std::lock_guard保证即当前线程 异常造成生命周期结束 它所管理的Mutex对象也一定会被解锁.但是需要注意的是,这样的lock应该被限制在可能之最短周期内,因为std::lock_guard会阻塞其他代码并行的机会。因此安插大括号令unlock尽量控制在最短的周期内.
下面我们来看一个完整的例子:
在我自己的电脑上面输出结果是:
"i want use my life to love"
"shihuawoaini"
"marryme"
但是如果没有使用std::lock_gard和std::mutex可能输出的结果就是乱七八糟了.线程1在打印的时候线程2也在打印线程3也在打印于是乎根本没有完整的句子.
递归的(Recursive) Lock:
上面的例子中std::recursive_mutex在 insertData()调用完后解锁.
尝试性的Lock以及带时间性的Lock:
有时候当前线程想要获得一个lock但是如果不成功的话并不想阻塞(block)当前线程.针对这种情况,mutex提供了成员函数try_lock(),该函数尝试锁住一个std::mutex对象,如果成功就返回true,如果失败就返回false(即使失败了也不会block当前线程).
但是需要注意的是try_lock也可能失败是因为该函数的原因,而并不是因为当前std::mutex被其他线程锁住了.
为了等待特定长的时间,我们可以选用(带时间性的)std::time_mutex,除此之外还有std::recursive_time_mutex他们都允许你调用try_lock_for()或try_lock_until(),用以等待一个时间段,或者等待直到某个时间点.
处理多个Lock:
通常一个线程一次只锁定一个std::mutex.但是有时候必须锁住多个mutex。拿前面说讲的各种加锁方式来说,当面临多个互斥量(mutex)的时候就会变的复杂且有风险;你或许取得了第一个的lock,但是却拿不到第二个,这样就发生deadlock.
比如上面的代码就有可能锁死,因为task_a可能锁住了一个std::mutex, task_b可能锁住了另外一个std::mutex.这样两个线程会就相互等待.
为了解决上面例子中的问题C++标准库给出了std::lock():
我们可以这样:
std::lock
std::lock会锁住(lock)它收到的所有mutex,如果所接受的这些Mutex中如果有的已经被其他线程锁住了,那么会解锁已经被他锁住的Mutex,等他其他线程unlock,且阻塞(blocking)当前线程直到它所接收的这些Mutex全部在当前线程被上锁或者抛出异常.如果是抛出异常会解锁(unlock)所有已经被锁住的Mutex。
也就是说std::lock()这个函数主要是用来防止需要在一个线程内锁住(lock)多个mutex的时候产生deadlock.
标准库还提供了全局函数 std::try_lock会尝试锁住它收到的所有std::mutex:
std::try_lock
std::try_lock尝试锁住它接收的所有的Mutex对象,如果所接收的所有Mutex并没有都被上锁成功(lock),则会返回第一个上锁失败的std::mutex的索引(从0开始计,以参数传递的顺序),而且解锁所有的已经被当前线程通过当前std::try_lock上锁了的Mutex.如果成功则返回-1。需要注意的是该函数并不会造成当前线程阻塞(也就是说如果接受的所有Mutex中有的在其他线程已经被block,当前线程也不会等待直到其他线程解锁,std::try_lock会直接返回第一个无法上锁的std::mutex对应的参数列表的下标)。
还有特别需要注意的是无论我们是用std::lock还是std::try_lock都会把这些上过锁完成的std::mutex adopt(过继)给std::lock_guard.
class std::lock_guard
接受一个已经在当前线程被上锁(block)了的mutex对象或者接受一个没有上锁的对象在构造函数中并在std::lock_guard的构造函数中给他上锁,如果该mutex对象已经被其他线程上锁了,那么阻塞(block)当前线程直到获得上锁权。
构造函数:
由class std::lock_guard的构造函数我们可以看出来它是不能被拷贝的,也不能被移动的.如果接受一个已经被上锁(block)的std::mutex的时候需要指出第二个参数std::adopt_lock_t.
class std::unique_lock
在了解class std::unique_lock之前我们首先了解一下用于构造函数的tag:
1, 在没有指定任何tag的情况下对当前class std::unique_lock的构造函数运行时候给它所接受的mutex上锁(block),如果当前mutex被其他线程锁住了那么会阻塞当前线程.
2,try_to_lock这个tag, 调用当前接受的mutex的try_lock()函数.(不会造成当前线程的阻塞).
3, deferred_lock这个tag,只是初始化当前class std::unique_lock对象,并不锁住它所接受的mutex参数.
4,adopt_lock这个tag,表明当前class std::unique_lock接受的mutex已经在当前线程被上锁了,现在由当前class std::unique_lock来接手管理权.
std::unique_lock::lock
对所管理的mutex对象调用lock(),如果该mutex被其他线程在锁着那么阻塞当前线程.
std::unique_lock::try_lock
调用所管理的mutex对象的try_lock().
std::unique_lock::try_lock_for
调用所管理的mutex对象的try_lock_for(),但是该mutex对象必须支持try_lock_for()。
std::unique_lock::try_lock_until
调用所管理的mutex对象的try_lock_until(),但是该对象必须支持try_lock_until().
std::unique_lock::unlock
解锁所管理的mutex对象,如果当前对象被std::unique_lock管理的时候被上锁了那么解锁该对象,如果在当前线程内当前std::unique_lock所管理的对象没被上锁,那么throw exception.
std::unique_lock::mutex
该函数返回一个指针所管理的成员函数的指针.需要注意的是:虽然返回了一个指向他所管理的mutex对象的指针,但是它并没有解锁(unlock)它所管理的mutex,也没有释放它对mutex对象的拥有权.
std::unique_lock::release
该函数返回他所管理的mutex的指针,返回后不再具有mutex的拥有权,如果返回前该mutex在当前线程被上锁了,那么返回后也处于上锁(lock)状态.
-----------------------------我是分割线-------------------------------------
简单使用Mutex和Lock:
假设我们有 int val 我们想在并发访问(concurrent access)中保护该 val,下面是一个比较粗浅的做法.
int val; std::mutex valMutex;
以后每次访问 val 都必须锁住 valMutex 以求独占.下面是一个很不粗糙的做法:
//线程1: //如果线程1先执行了那么线程2就会block直到线程1种的valMutex解锁. valMutex.lock(); if(val >= 0){ f(val); }else{ f(-val); } valMutex.unlock(); //线程2: valMutex.lock(); ++val; valMutex.unlock();
从上面我们可以看出来,凡是可能发生并发访问(concurrent access)的地方都需要使用同一个std::mutex,不论是读写都是如此.但是上面也随之带来了问题,当上面的某个线程throw exception的时候,就会造成资源(比如上面例子中的 val 永远被锁住)。
C++标准库提供了更好的解决方案:
int val; std::mutex valMutex;
...
std::lock_guard<std::mutex> lg(valMutex); //注意这里
if(val >= 0){
f(val);
}else{
f(-val)
}
上面的std::lock_guard保证即当前线程 异常造成生命周期结束 它所管理的Mutex对象也一定会被解锁.但是需要注意的是,这样的lock应该被限制在可能之最短周期内,因为std::lock_guard会阻塞其他代码并行的机会。因此安插大括号令unlock尽量控制在最短的周期内.
int val; std::mutex valMutex;
...
{
{
std::lock_guard<std::mutex> lg(valMutex);
if(val >= 0){
f(val);
}else{
f(-val);
}
} //lg在这个大括号结束的时候解锁valMutex.
}
下面我们来看一个完整的例子:
#include <iostream> #include <future> #include <mutex> #include <string> std::mutex printMutex; void print(const std::string& str) { std::lock_guard<std::mutex> autoLock(printMutex); for (const char& cr : str) { std::cout.put(cr); } std::cout << std::endl; } int main() { std::future<void> result1 = std::async(std::launch::async, print, "shihuawoaini"); std::future<void> result2 = std::async(std::launch::async, print, "marryme"); print("i want use my life to love you"); return 0; }
在我自己的电脑上面输出结果是:
"i want use my life to love"
"shihuawoaini"
"marryme"
但是如果没有使用std::lock_gard和std::mutex可能输出的结果就是乱七八糟了.线程1在打印的时候线程2也在打印线程3也在打印于是乎根本没有完整的句子.
递归的(Recursive) Lock:
class DatabaseAccess{ private: std::recursive_mutex mutex; public: void insertData(...) { std::lock_guard<std::recursive_mutex> lg(mutex); ... } void createTableAndInsertData() { std::lock_guard<std::recursive_mutex> lg(mutex); insertData(...); //OK不会再锁死了. }
上面的例子中std::recursive_mutex在 insertData()调用完后解锁.
尝试性的Lock以及带时间性的Lock:
有时候当前线程想要获得一个lock但是如果不成功的话并不想阻塞(block)当前线程.针对这种情况,mutex提供了成员函数try_lock(),该函数尝试锁住一个std::mutex对象,如果成功就返回true,如果失败就返回false(即使失败了也不会block当前线程).
但是需要注意的是try_lock也可能失败是因为该函数的原因,而并不是因为当前std::mutex被其他线程锁住了.
std::mutex m; while(m.try_lock() == false){ doOtherStuff(); } std::lock_guard<std::mutex> lg(m, std::adopt_lock); ...
为了等待特定长的时间,我们可以选用(带时间性的)std::time_mutex,除此之外还有std::recursive_time_mutex他们都允许你调用try_lock_for()或try_lock_until(),用以等待一个时间段,或者等待直到某个时间点.
std::timed_mutex m; if(m.try_lock_for(std::chrono::seconds(1))){ std::lock_guard<std::time_mutex> lg(m, std::adopt_lock); }else{ doOtherThing(); }
处理多个Lock:
通常一个线程一次只锁定一个std::mutex.但是有时候必须锁住多个mutex。拿前面说讲的各种加锁方式来说,当面临多个互斥量(mutex)的时候就会变的复杂且有风险;你或许取得了第一个的lock,但是却拿不到第二个,这样就发生deadlock.
// std::lock example #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::lock std::mutex foo,bar; void task_a () { foo.lock(); bar.lock(); std::cout << "task a\n"; foo.unlock(); bar.unlock(); } void task_b () { bar.lock(); foo.lock(); std::cout << "task b\n"; bar.unlock(); foo.unlock(); } int main () { std::thread th1 (task_a); std::thread th2 (task_b); th1.join(); th2.join(); return 0; }
比如上面的代码就有可能锁死,因为task_a可能锁住了一个std::mutex, task_b可能锁住了另外一个std::mutex.这样两个线程会就相互等待.
为了解决上面例子中的问题C++标准库给出了std::lock():
我们可以这样:
std::mutex m1; std::mutex m2; ... { std::lock(m1, m2); std::lock_guard<std::mutex> lockM1(m1, std::adopt_lock); std::lock_guard<std::mutex> lockM2(m2, std::adopt_lock); ... }
std::lock
template <class Mutex1, class Mutex2, class... Mutexes> void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
std::lock会锁住(lock)它收到的所有mutex,如果所接受的这些Mutex中如果有的已经被其他线程锁住了,那么会解锁已经被他锁住的Mutex,等他其他线程unlock,且阻塞(blocking)当前线程直到它所接收的这些Mutex全部在当前线程被上锁或者抛出异常.如果是抛出异常会解锁(unlock)所有已经被锁住的Mutex。
也就是说std::lock()这个函数主要是用来防止需要在一个线程内锁住(lock)多个mutex的时候产生deadlock.
标准库还提供了全局函数 std::try_lock会尝试锁住它收到的所有std::mutex:
std::mutex m1; std::mutex m2; int idx = std::try_lock(m1, m2); if(idx < 0){ std::lock_guard<std::mutex> lockM1(m1, std::adopt_lock); std::lock_guard<std::mutex> lockM2(m2, std::adopt_lock); }else{ std::cerr << "could not all mutexs" << id+1 << std::endl; }
std::try_lock
template <class Mutex1, class Mutex2, class... Mutexes> int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
std::try_lock尝试锁住它接收的所有的Mutex对象,如果所接收的所有Mutex并没有都被上锁成功(lock),则会返回第一个上锁失败的std::mutex的索引(从0开始计,以参数传递的顺序),而且解锁所有的已经被当前线程通过当前std::try_lock上锁了的Mutex.如果成功则返回-1。需要注意的是该函数并不会造成当前线程阻塞(也就是说如果接受的所有Mutex中有的在其他线程已经被block,当前线程也不会等待直到其他线程解锁,std::try_lock会直接返回第一个无法上锁的std::mutex对应的参数列表的下标)。
还有特别需要注意的是无论我们是用std::lock还是std::try_lock都会把这些上过锁完成的std::mutex adopt(过继)给std::lock_guard.
class std::lock_guard
接受一个已经在当前线程被上锁(block)了的mutex对象或者接受一个没有上锁的对象在构造函数中并在std::lock_guard的构造函数中给他上锁,如果该mutex对象已经被其他线程上锁了,那么阻塞(block)当前线程直到获得上锁权。
构造函数:
explicit lock_guard (mutex_type& m); lock_guard (mutex_type& m, adopt_lock_t tag); lock_guard (const lock_guard&) = delete;
由class std::lock_guard的构造函数我们可以看出来它是不能被拷贝的,也不能被移动的.如果接受一个已经被上锁(block)的std::mutex的时候需要指出第二个参数std::adopt_lock_t.
class std::unique_lock
在了解class std::unique_lock之前我们首先了解一下用于构造函数的tag:
1, 在没有指定任何tag的情况下对当前class std::unique_lock的构造函数运行时候给它所接受的mutex上锁(block),如果当前mutex被其他线程锁住了那么会阻塞当前线程.
2,try_to_lock这个tag, 调用当前接受的mutex的try_lock()函数.(不会造成当前线程的阻塞).
3, deferred_lock这个tag,只是初始化当前class std::unique_lock对象,并不锁住它所接受的mutex参数.
4,adopt_lock这个tag,表明当前class std::unique_lock接受的mutex已经在当前线程被上锁了,现在由当前class std::unique_lock来接手管理权.
unique_lock() noexcept;//默认构造函数不管理任何mutex. explicit unique_lock (mutex_type& m);//管理一个未被上锁的mutex并在构造函数里给它上锁.如果该mutex在其他线程 // 被锁定了那么阻塞当前线程. unique_lock (mutex_type& m, try_to_lock_t tag);//调用m的try_lock()函数,不会造成当前线程阻塞. unique_lock (mutex_type& m, defer_lock_t tag) noexcept;//用m构造当前unique_lock但是不在构造函数中给m上锁 unique_lock (mutex_type& m, adopt_lock_t tag);//m已经在当前线程被上锁了,现在来接手m的管理权. template <class Rep, class Period> unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time); //注意这个构造函数调用m的try_lock_for()函数,也就是说m只能是std::time_mutex, std::recursive_time_mutex. //尝试锁住m在规定的时间段内. //如果m此时没有被任何线程锁住那么直接锁住m. //如果m此时被其他线程锁住了,且等待超时了,其他线程也没有unlock m.那么接着运行当前线程下面的内容. //如果m此时被其他线程锁住了,但是很快就unlock m了并没有超过规定时间也接着运行当前线程下面的内容. template <class Clock, class Duration> unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time); //注意这个够长函数调用m的try_lock_until()函数,也就是说m只能是std::time_mutex, std::recursive_time_mutex. //尝试在规定时间点之前锁住m. //如果m此时没有被任何其他线程锁住,那么直接锁住m. //如果m此时被其他线程锁住了,且等待超过了给定的时间点也没有unlock m, 那么接着运行当前线程下面的内容. //如果m此时被其他线程锁住了,且很快m就被unlock了没有超过规定的时间点也接着运行当前线程下面的内容. unique_lock (const unique_lock&) = delete;//不能拷贝. unique_lock (unique_lock&& x);//只支持移动
std::unique_lock::lock
void lock();
对所管理的mutex对象调用lock(),如果该mutex被其他线程在锁着那么阻塞当前线程.
std::unique_lock::try_lock
bool try_lock();
调用所管理的mutex对象的try_lock().
std::unique_lock::try_lock_for
template <class Rep, class Period> bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
调用所管理的mutex对象的try_lock_for(),但是该mutex对象必须支持try_lock_for()。
std::unique_lock::try_lock_until
template <class Clock, class Duration> bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);
调用所管理的mutex对象的try_lock_until(),但是该对象必须支持try_lock_until().
std::unique_lock::unlock
void unlock();
解锁所管理的mutex对象,如果当前对象被std::unique_lock管理的时候被上锁了那么解锁该对象,如果在当前线程内当前std::unique_lock所管理的对象没被上锁,那么throw exception.
std::unique_lock::mutex
mutex_type* mutex() const noexcept;
该函数返回一个指针所管理的成员函数的指针.需要注意的是:虽然返回了一个指向他所管理的mutex对象的指针,但是它并没有解锁(unlock)它所管理的mutex,也没有释放它对mutex对象的拥有权.
#include <iostream> #include <thread> #include <mutex> class my_mutex : public std::mutex { private: mutable unsigned int id; public: my_mutex(const unsigned int& ID) :id(ID) {} my_mutex() = default; virtual ~my_mutex() = default; unsigned int get_id()const noexcept; }; unsigned int my_mutex::get_id()const noexcept { return (this->id); } my_mutex mutex(101); void print_ids(const int& id) { std::unique_lock<my_mutex> u_lock(mutex); std::cout << "thread# " << id << " locked mutex " << u_lock.mutex()->get_id() << '\n'; } int main() { std::thread threads[10]; for (unsigned int i = 0; i < 10; ++i) { threads[i] = std::thread(print_ids, i); } for (std::thread& ref_thread : threads) { ref_thread.join(); } return 0; }
std::unique_lock::release
mutex_type* release() noexcept;
该函数返回他所管理的mutex的指针,返回后不再具有mutex的拥有权,如果返回前该mutex在当前线程被上锁了,那么返回后也处于上锁(lock)状态.
#include <iostream> #include <thread> #include <mutex> #include <vector> std::mutex mutex; int count = 0; void print_count_and_unlock(std::mutex* mutex) { std::cout << "count: " << count << '\n'; mutex->unlock(); } void task() { std::unique_lock<std::mutex> u_lock(mutex); ++count; print_count_and_unlock(u_lock.release()); } int main() { std::vector<std::thread> threads; for (int i = 0; i < 10; ++i) { threads.push_back(std::thread(task)); } for (std::thread& ref_thread : threads) { ref_thread.join(); } return 0; }
相关文章推荐
- mac 配置sublime text的C语言编译系统
- C++: 为数据类型定义别名的方式
- C++连接MySQL数据库
- [C++] 利用模板的模板参数实现单链表
- 快速排序(C/C++版)
- utilities——C++常用仿函数(二)
- 《Effective C++》学习笔记——条款42
- va_start和va_end使用详解
- 嵌入式工程师应该知道的C语言
- VS2015安装与C++进行简单单元测试
- C语言结构体之指针访问
- C++第2次实验(成年男性的标准体重)
- 设计模式C++学习笔记之十九(State状态模式)
- 利用C语言实现动态顺序表
- 设计模式C++学习笔记之十八(Visitor访问者模式)
- 设计模式C++学习笔记之十七(Chain of Responsibility责任链模式)
- C++实现动态顺序表
- 设计模式C++学习笔记之十四(Iterator迭代器模式)
- c语言经典大题
- 设计模式C++学习笔记之十三(Decorator装饰模式)