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; }