Mutex全名mutual exclusion(互斥体),是一个可加锁对象(lockable object),用来协助采起独占排他方式控制对资源的并发访问.这里的资源多是个object,也多是多个object的组合。为了得到独占式的资源访问能力,相应的线程必须锁定(lock)mutex,这样能够防止其余线程也锁定mutex,直到第一个解锁mutex的线程.ios
-----------------------------我是分割线-------------------------------------并发
简单使用Mutex和Lock:async
假设咱们有 int val 咱们想在并发访问(concurrent access)中保护该 val,下面是一个比较粗浅的作法.函数
int val; std::mutex valMutex;
之后每次访问 val 都必须锁住 valMutex 以求独占.下面是一个很不粗糙的作法:this
//线程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 永远被锁住)。spa
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. }
下面咱们来看一个完整的例子:code
#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; }