C++11多執行緒——lock詳解
C++11提供了兩種管理鎖的類
- std::lock_guard:與mutex RAII相關,方便執行緒對互斥量上鎖
- std::unique_lock: 與mutex RAII相關,方便執行緒對互斥量上鎖,相比std::lock_guard提供了更好的上鎖和解鎖控制
一 lock_guard詳解
- lock_guard是一個模板類:template<classMutex>class lock_guard;
-
lock_guard物件通常用來管理某個鎖(Lock)物件,與Mutex RALL相關,方便執行緒對互斥量上鎖,在其宣告週期內,它管理的鎖一直保持上鎖狀態;在其宣告週期結束之後,它鎖管理的鎖會被自動釋放(即用建構函式對鎖物件上鎖,解構函式對鎖物件解鎖)
-
模板引數Mutex代表幾種基本的BasicLockable型別分別為:std::mutex, std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex以及 std::unique_lock, (BasicLockable型別只需滿足兩種操作,lock和unlock。Lockable型別在BasicLockable的基礎上增加了try_lock操作。TimedLockable型別在Lockable的基礎上又增加了try_lock_for和try_lock_until操作。)
-
注意:lock_guard物件並不負責管理
-
lock_guard建構函式如下所示
-
locking-constructor (a) exlicit lock_guard(mutex_type& m);
adopting-constructor (b) lock_guard(mutex_type&m,adopt_lock_ttag);
copy(deleted) -constructor (c) lock_guard(const lock_guard&) = delete;
a locking-constructor
lock_guard物件管理Mutex物件m,並在構造時對m上鎖
b adopting-constructor初始化
lock_guard物件管理Mutex物件m,與locking初始化不同的是,Mutex物件以被當前執行緒鎖住。(將mutex物件用adopting::lock_guard管理,最終在呼叫lock_guard解構函式時,m鎖物件會被自動解鎖)
c copy-constructor
lock_guard的拷貝構造與移動構造均被禁用
-
locking-constructor examples
#include <iostream> #include <thread> #include <mutex> #include <stdexcept> std::mutex mtx; void printEven(int x) { if (0 == x % 2) { std::cout << x << " is even\n"; } else { throw (std::logic_error("not even\n")); } } void printThreadID(int id) { try { std::lock_guard<std::mutex>lck(mtx); printEven(id); } catch (std::logic_error&e) { //std::cout << e.what() << std::endl; std::cout << "[exception caught]\n"; } } int main(int argc, _TCHAR* argv[]) { std::thread threads[10]; for (int i = 0; i < 10;++i) { threads[i] = std::thread(printThreadID, i + 1); } for (auto &th : threads) { th.join(); } return 0; }
在voidprintThreadID(int id)中,首先捕捉voidprintEven(int x)中丟擲的異常,在try塊內,首先對mtx鎖物件構造lock_guard物件lck(此語句之後,mtx鎖物件由lck管理),即在try塊作用域內(也就是lck物件生命週期內),mtx鎖物件被上鎖,在lck生命週期結束時mtx鎖物件自動解鎖(在丟擲異常時,依舊可正確解鎖)。
-
adopting-constructor example
#include <iostream> #include <thread> #include <mutex> std::mutex mtx; void printThreadID(int id) { mtx.lock(); std::lock_guard<std::mutex>lck(mtx, std::adopt_lock); std::cout << "thread # " << id << '\n'; } void testAdoptingConstructor() { std::thread threads[10]; for (int i = 0; i < 10; ++i) { threads[i] = std::thread(printThreadID, i + 1); } for (auto& th : threads) { th.join(); } } int main(int argc, _TCHAR* argv[]) { testAdoptingConstructor(); return 0; }
在void printThreadID(int id)中,首先對mtx上鎖,之後呼叫lock_guard的adopting-constructor來構造lck,用lck管理mtx鎖物件。(注意,std::adopt_lock表明當前執行緒已獲得鎖)
二 std::unique_lock
- std::unique_lock簡介
std::unique_lock與std::lock_guard一樣用來管理鎖物件(在丟擲異常之前上鎖的物件使用unique_lock管理的鎖物件也可正常解鎖,可一定程度上避免死鎖),其與std::lock_guard類似,但是給程式設計師提供了足夠的靈活度。
在構造時,unique_lock對相關需要一個Mutex鎖物件作為其引數,新建立的unique_lock物件負責傳入的Mutex鎖物件的上鎖和解鎖操作
unique_lock與lock_guard一樣,不負責管理Mutex鎖物件生命週期
- std::unique_lock建構函式
default(a) unique_lock()noexcept;
locking(b) explicit unique_lock(mutex_type&m);
try_locking(c) unique_lock(mutex_type&m,try_to_lock_t tag);
deferred(d) unique_lock(mutex_type&m, defer_lock_t tag)noexcept;
adopting(e) unique_lock(mutex_type&m,adopt_lock_ttag);
lookingfor(f) template <classRep,class Period>unique_lock(mutex_type&m,const chrono::duration<Rep,Period>&rel_time);
locking until(g) template<class Clock,classDuration>unique_lock(mutex_type& m,const chrono::time_point<Clock,Duration>&abs_time);
copy[delete](h) unique_lock(const unique_lock&) = delete;
move(i) unique_lock(unique_lock&&x);
- adefault_constructor
新建立的unique_lock物件不管理任何Mutex鎖物件
- b locking constructor
新建立的unique_lock物件管理鎖物件m,並呼叫m.lock()對m上鎖,若另外某個unique_lock管理了該Mutex鎖物件m,則當前執行緒會被阻塞。
- c try_locking constructor
新建立的unique_lock物件管理鎖物件m,呼叫m.try_lock()嘗試上鎖,若上鎖失敗,並不會阻塞當前執行緒。
- d deferred constructor
新建的unique_lock物件管理Mutex鎖物件m,初始化時並不鎖住Mutex鎖物件m, m是一個沒有被當前執行緒鎖住的Mutex物件呢
- e adopting constructor
新建立的unique_lock管理Mutex鎖物件m,m是已經被當前執行緒鎖住的Mutex物件,並且新建立的unique_lock物件擁有對鎖的所有權
- flocking for constructor
新建立的unique_lock管理Mutex鎖物件m,並試圖通過m.try_lock_for(read_time)來鎖住Mutex物件一段時間
- g locking until constructor
新建立的unique_lock管理Mutex鎖物件m,並試圖通過m.try_lock_until(abs_time)在某個時間點之前鎖住Mutex鎖物件m
- h copy constructor —— deleted
unique_lock物件不能被拷貝構造
- i move constructor
新建立的unique_lock物件擁有x所管理的Mutex鎖物件的所有權。而此時x物件如預設建構函式鎖建立的unique_lock物件一樣,不管理任何Mutex鎖物件
- 總結:由b、e建立的unique_lock物件通常擁有Mutex物件的鎖,通過 a、d建立的unique_lock物件不會擁有鎖。通過 c、f、g建立的unique_lock在lock成功時獲得鎖
- std::unique_lock constructor examples
#include <iostream> #include <thread> #include <mutex> std::mutex mtxOne,mtxTwo; void taskA() { std::lock(mtxOne,mtxTwo); std::unique_lock<std::mutex>lck1(mtxOne, std::adopt_lock); std::unique_lock<std::mutex>lck2(mtxTwo, std::adopt_lock); std::cout << "taskA\n"; } void taskB() { std::unique_lock<std::mutex>lck1, lck2; lck1 = std::unique_lock<std::mutex>(mtxOne, std::defer_lock); lck2 = std::unique_lock<std::mutex>(mtxTwo, std::defer_lock); std::lock(lck1, lck2); std::cout << "taskB\n"; } int main(int argc, _TCHAR* argv[]) { std::thread th1(taskA); std::thread th2(taskB); th1.join(); th2.join(); return 0; }
- std::unique(移動)賦值操作
- 函式原型
move(a) unique_lock& operator=(unique_lock&& x)noexcept
copy[deleted](b) unique_lock& operator=(unique_lock&) = delete
- 詳解:
move assignment:移動賦值之後,有x所管理的鎖物件及其及其狀態被新的std::unique_lock取代。如果被賦值std::unique_lock物件之前已經獲得了其他Mutex物件的鎖,則在移動賦值之前呼叫unlock成員函式釋放其鎖佔用的鎖。而x如預設建構函式構造的std::unique_lock物件一樣,不在管理任何Mutex鎖物件。
- std::unique_lock move assignment examples
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock std::mutex mtx; // mutex for critical section void print_fifty(char c) { std::unique_lock<std::mutex> lck; // default-constructed lck = std::unique_lock<std::mutex>(mtx); // move-assigned for (int i = 0; i<50; ++i) { std::cout << c; } std::cout << '\n'; } int main() { std::thread th1(print_fifty, '*'); std::thread th2(print_fifty, '$'); th1.join(); th2.join(); return 0; }
-
std::unique_lock member functions
- 上鎖/解鎖:lock、try_lock、try_lock_for、try_lock_until、unlock
- 獲取屬性:owns_lock(返回unique_lock物件是否獲得鎖,若獲得鎖則返回true,否則返回false),operator bool(與owns_lock一樣,大多用於條件判斷),mutex返回當前unique_lock物件所管理的Mutex物件的指標
- 修改操作:移動賦值,swap(與另一個unique_lock物件交換他們所管理的Mutex鎖物件的所有權),release(釋放unique_lock管理的Mutex物件的所有權,並返回之前管理的Mutex物件的指標)
std::unique_lock::lock詳解:
對std::unique_lock所管理的鎖物件上鎖,若在呼叫lock時其他執行緒以對該Mutex物件已被其他執行緒鎖住,當前執行緒被阻塞直至它獲得了鎖。改函式返回時,代表std::unique_lock物件已經擁有它所管理的Mutex物件的鎖,如果上鎖失敗,則丟擲system_error異常。
// unique_lock::lock/unlock #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock, std::defer_lock std::mutex mtx; // mutex for critical section void print_thread_id(int id) { std::unique_lock<std::mutex> lck(mtx, std::defer_lock); // critical section (exclusive access to std::cout signaled by locking lck): lck.lock(); std::cout << "thread #" << id << '\n'; lck.unlock(); } int main() { std::thread threads[10]; // spawn 10 threads: for (int i = 0; i<10; ++i) threads[i] = std::thread(print_thread_id, i + 1); for (auto& th : threads) th.join(); return 0; }
std::unique_lock::try_lock
對std::unique_lock所管理的Mutex物件上鎖,如果上鎖成功則返回true,否則返回false
// unique_lock::try_lock example #include <iostream> // std::cout #include <vector> // std::vector #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock, std::defer_lock std::mutex mtx; // mutex for critical section void print_star() { std::unique_lock<std::mutex> lck(mtx, std::defer_lock); // print '*' if successfully locked, 'x' otherwise: if (lck.try_lock()) std::cout << '*'; else std::cout << 'x'; } int main() { std::vector<std::thread> threads; for (int i = 0; i<500; ++i) threads.emplace_back(print_star); for (auto& x : threads) x.join(); return 0; }
- std::unique_lock::try_lock_for
對std::unique_lock所管理的Mutex物件上鎖,如果上鎖成功則返回true,否則返回false
// unique_lock::try_lock_for example #include <iostream> // std::cout #include <chrono> // std::chrono::milliseconds #include <thread> // std::thread #include <mutex> // std::timed_mutex, std::unique_lock, std::defer_lock std::timed_mutex mtx; void fireworks() { std::unique_lock<std::timed_mutex> lck(mtx, std::defer_lock); // waiting to get a lock: each thread prints "-" every 200ms: while (!lck.try_lock_for(std::chrono::milliseconds(200))) { std::cout << "-"; } // got a lock! - wait for 1s, then this thread prints "*" std::this_thread::sleep_for(std::chrono::milliseconds(1000)); std::cout << "*\n"; } int main() { std::thread threads[10]; // spawn 10 threads: for (int i = 0; i<10; ++i) threads[i] = std::thread(fireworks); for (auto& th : threads) th.join(); return 0; }
std::unique_lock::try_lock_until
對std::unique_lock所管理的Mutex物件上鎖,如果上鎖成功則返回true,否則返回false
// timed_mutex::try_lock_until example #include <iostream> // std::cout #include <chrono> // std::chrono::system_clock #include <thread> // std::thread #include <mutex> // std::timed_mutex #include <ctime> // std::time_t, std::tm, std::localtime, std::mktime std::timed_mutex cinderella; // gets time_point for next midnight: std::chrono::time_point<std::chrono::system_clock> midnight() { using std::chrono::system_clock; std::time_t tt = system_clock::to_time_t(system_clock::now()); struct std::tm * ptm = std::localtime(&tt); ++ptm->tm_mday; ptm->tm_hour = 0; ptm->tm_min = 0; ptm->tm_sec = 0; return system_clock::from_time_t(mktime(ptm)); } void carriage() { std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock); if (lck.try_lock_until(midnight())) { std::cout << "ride back home on carriage\n"; lck.unlock(); } else std::cout << "carriage reverts to pumpkin\n"; } void ball() { std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock); lck.lock(); std::cout << "at the ball...\n"; } int main() { std::thread th1(ball); std::thread th2(carriage); th1.join(); th2.join(); return 0; }
std::unique_lock::release
釋放std::unique_lock所管理物件的所有權,並返回指向其管理Mutex物件的指標(注意,std::unique_lock::release只釋放所有權,不解鎖)
// unique_lock::release example #include <iostream> // std::cout #include <vector> // std::vector #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock std::mutex mtx; int count = 0; void print_count_and_unlock(std::mutex* p_mtx) { std::cout << "count: " << count << '\n'; p_mtx->unlock(); } void task() { std::unique_lock<std::mutex> lck(mtx); ++count; print_count_and_unlock(lck.release()); } int main() { std::vector<std::thread> threads; for (int i = 0; i<10; ++i) threads.emplace_back(task); for (auto& x : threads) x.join(); return 0; }
std::unique_lock::owns_lock
返回std::unique_lock物件是否獲得了它所管理的Mutex鎖物件的鎖,若std::unique_lock已獲得Mutex物件的鎖,則返回true,否則返回false;
// unique_lock::operator= example #include <iostream> // std::cout #include <vector> // std::vector #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock, std::try_to_lock std::mutex mtx; // mutex for critical section void print_star() { std::unique_lock<std::mutex> lck(mtx, std::try_to_lock); // print '*' if successfully locked, 'x' otherwise: if (lck.owns_lock()) std::cout << '*'; else std::cout << 'x'; } int main() { std::vector<std::thread> threads; for (int i = 0; i<500; ++i) threads.emplace_back(print_star); for (auto& x : threads) x.join(); return 0; }
std::unique_lock::operator bool
std::unique_lock::operator bool與std::unique_lock::owns_lock功能相同。
// unique_lock::operator bool #include <iostream> // std::cout #include <vector> // std::vector #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock, std::try_to_lock std::mutex mtx; // mutex for critical section void print_star() { std::unique_lock<std::mutex> lck(mtx, std::try_to_lock); // print '*' if successfully locked, 'x' otherwise: if (lck) std::cout << '*'; else std::cout << 'x'; } int main() { std::vector<std::thread> threads; for (int i = 0; i<500; ++i) threads.emplace_back(print_star); for (auto& x : threads) x.join(); return 0; }
- std::unique_lock::mutex
此成員函式只是簡單的返回指向std::unique_lock管理的Mutex鎖物件的指標,這裡不多做介紹