c++ 學習之 多線程(六)lock_guard 和 unique_lock
前言
在使用mutex互斥量時,總會出現lock後沒有unlock的情況,尤其是在判斷分支中,某些被不常進入的分支忘記unlock,我們可以用RAII機制的模板類來解決忘記unlock的問題。
正文
1.std::lock_guard
std::lock_gurad 是 C++11 中定義的模板類,定義如下:
template <class Mutex> class lock_guard;
#include<stdio.h>
#include<thread>
#include<mutex>
using namespace std;
mutex m;
void fun(int& a)
{
for (int i = 0; i < 100000; i++)
{
lock_guard<mutex>m_guard(m);
a++;
}
}
int main()
{
int a = 0;
thread t1(fun, ref(a));
thread t2(fun, ref(a));
t1.join();
t2.join();
printf("%d\n", a);
}
這種方法創建出來的lock_guard接管的mutex對象應該是未被加鎖的,創建時調會在構造函數中對mutex對象加鎖,生命週期結束調用析構函數時解鎖。
for (int i = 0; i < 100000; i++)
{
m.lock();
lock_guard<mutex>m_guard(m,adopt_lock);
a++;
}
}
也可以加adopt_lock參數,接管已被lock的mutex對象。注意,不管是哪種方式構造出的lock_guard,都不要再調用mutex對象的lock(),unlock()了(雖然只要保證lock和unlock的順序和次數就不會出問題,但是沒必要這樣用)。
2.std::unique_lock
unique_lock也是 C++ 11 提供的模板了,完全代替lock_guard 的功能,並且更加靈活,功能更加豐富,但是相應的消耗比lock_guard大,效率低一些。定義如下:
template<class Mutex>class unique_lock;
unique_lock 有下面幾種構造方式:
(一)unique_lock() noexcept;
這種默認的構造函數,構造出來的對象不管理任何 mutex 對象。
(二) explicit unique_lock (mutex_type& m);
這種構造函數構造出來的unique_lock 對象接管一個沒有lock的mutex對象,並且在構造函數中調用mutex對象的lock函數,失敗會阻塞,直到lock成功。由於加了explicit ,這種構造方式只能顯式的構造。
#include<stdio.h>
#include<thread>
#include<mutex>
using namespace std;
mutex m;
void fun(int &a)
{
for (int i = 0; i < 100000; i++)
{
unique_lock<mutex>unique_m(m);
a++;
}
}
int main()
{
int a = 0;
thread t1(fun, ref(a));
thread t2(fun, ref(a));
t1.join();
t2.join();
printf("%d\n", a);
}
(三)unique_lock (mutex_type& m, try_to_lock_t tag);
這樣的構造函數,會在構造函數中調用mutex對象的try_lock函數,鎖失敗會立刻返回。
(四)unique_lock (mutex_type& m, defer_lock_t tag) noexcept;
這樣構造出來的unique_lock 只是單純的接管mutex對象,不會上鎖。
(五)unique_lock (mutex_type& m, adopt_lock_t tag);
這種構造會接管一個已經lock的mutex對象,就是在構造函數中不再調用mutex的lock函數。
(六)
template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
這種構造函數會調用try_lock_for,在rel_time這個時間段內嘗試lock接管的mutex對象,超時會立即返回。
(七)
template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
這種構造函數會調用try_lock_until,在abs_time這個時間點之前嘗試lock接管的mutex對象,超時立即返回。
(八)unique_lock (unique_lock&& x);
更換mutex所有權,新創建出來的unique_lock對象會接管參數中對象的mutex對象。
其中,(二)(五)兩種在創建出對象後,mutex對象是lock狀態的,(一)(四)是非lock狀態的,其他的不定,不管任何mutex對象 ,一旦被 unique_lock對象接管後,不允許其他unique_lock對象接管了。
unique_lock 的其他成員函數
lock()
unlock()
try_lock()
try_lock_for()
try_lcok_until()
相較於lock_guard,unique_lock 可以隨時的lock和unlock,更加靈活,try_lock_for()會在一段時間內嘗試lock對象,超時立刻返回。
try_lcok_until()會在時間點之前嘗試lcok對象,超時立刻返回。
unique_lock交換所有權
可以用std::move 或者臨時對象來轉換歸屬權
unique_lock<mutex>unique_m(m);
unique_lock<mutex>unique_n(n);
unique_n = move(unique_m);
unique_lock<mutex>unique_m(m);
unique_m = unique_lock<mutex>(n);
在交換所有權之前,左值會將自己所有的mutex對象unlock,然後接管右值的mutex對象,並且不會改變其狀態。
unique_lock釋放mutex對象
成員函數release可以釋放所管理的mutex對象,調用之後,mutex對象不再歸屬當前unique_lock對象。但是並不改變mutex對象的狀態,要區分release與unlock的區別。