視頻教程:https://www.bilibili.com/video/av92453755
std::lock_guard
- std::lock_guard嚴格基于作用域(scope-based)的鎖管理類模板,構造時是否加鎖是可選的(不加鎖時假定當前線程已經獲得鎖的所有權),析構時自動釋放鎖,所有權不可轉移,對象生存期內不允許手動加鎖和釋放鎖。
- 默認構造函數(shù)里鎖定互斥量,即調用互斥量的lock函數(shù)。
- 析構函數(shù)里解鎖互斥量,即調用互斥量的unlock函數(shù)。
- std::lock_guard 對象并不負責管理 Mutex 對象的生命周期,lock_guard 只是簡化了 Mutex 對象的上鎖和解鎖操作,方便線程對互斥量上鎖,即在某個 lock_guard對象的聲明周期內,它所管理的鎖對象會一直保持上鎖狀態(tài);而 lock_guard的生命周期結束之后,它所管理的鎖對象會被解鎖。
std::unique_lock
- 與std:::lock_gurad基本一致,但更加靈活的鎖管理類模板,構造時是否加鎖是可選的,在對象析構時如果持有鎖會自動釋放鎖,所有權可以轉移。對象生命期內允許手動加鎖和釋放鎖。但提供了更好的上鎖和解鎖控制,尤其是在程序拋出異常后先前已被上鎖的 Mutex 對象可以正確進行解鎖操作,極大地簡化了程序員編寫與 Mutex 相關的異常處理代碼****。
- std::unique_lock的上鎖/解鎖操作:lock,try_lock,try_lock_for,try_lock_until 和unlock
兩者區(qū)別
- std::unique_lock更靈活,提供了lock, unlock, try_lock等接口
- std::lock_guard更簡單,沒有多余的接口,構造函數(shù)時拿到鎖,析構函數(shù)時釋放鎖,但更省時
線程死鎖
因為std::lock_guard更簡單的特性,所以可能出現(xiàn)下列情況的線程死鎖
#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>
std::mutex mt1;
std::mutex mt2;
void deadLock(std::mutex& mtA, std::mutex& mtB)
{
std::lock_guard<std::mutex>lock1(mtA);
std::cout << "get the first mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::lock_guard<std::mutex>lock2(mtB);
std::cout << "get the second mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::cout << "do something in thread " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t1([&] {deadLock(mt1, mt2); });
std::thread t2([&] {deadLock(mt2, mt1); });
t1.join();
t2.join();
}
解決方式:使用鎖定策略+原子鎖方式來防止死鎖情況
方法一:先同時lock掉兩個鎖,再構建std::lock_guard
構建lock_guard時,Tag 參數(shù)為 std::adopt_lock,表明當前線程已經獲得了鎖,不需要再鎖了,此后mt對象的解鎖操作交由 lock_guard 對象 guard 來管理,在 guard 的生命周期結束之后,mt對象會自動解鎖。
#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>
#include <assert.h>
std::mutex mt1;
std::mutex mt2
void deadLockProcess1(std::mutex& mtA, std::mutex& mtB)
{
std::lock(mtA, mtB);
std::lock_guard<std::mutex>lock1(mtA, std::adopt_lock);
std::cout << "get the first mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::lock_guard<std::mutex>lock2(mtB, std::adopt_lock);
std::cout << "get the second mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::cout << "do something in thread " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t1([&] {deadLockProcess1(mt1, mt2); });
std::thread t2([&] {deadLockProcess1(mt2, mt1); });
t1.join();
t2.join();
}
上述例子使用std::unique_lock其實也可以,但這時使用如上面的lock_guard效率更高。
方法二:先構建std::unique_lock,再同時lock掉兩個鎖
構建unique_lock時,Tag 參數(shù)為 std::defer_lock,表明不lock鎖,在執(zhí)行功能代碼之前再統(tǒng)一lock掉兩個unique_lock,在 unique_lock 的生命周期結束之后,mt對象自動解鎖。
#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>
#include <assert.h>
std::mutex mt1;
std::mutex mt2;
void deadLockProcess2(std::mutex& mtA, std::mutex& mtB)
{
std::unique_lock<std::mutex>lock1(mtA, std::defer_lock);
std::cout << "get the first mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::unique_lock<std::mutex>lock2(mtB, std::defer_lock);
std::cout << "get the second mutex" << " in thread " << std::this_thread::get_id() << std::endl;
std::lock(lock1, lock2);
assert(lock1.owns_lock() == true);
std::cout << "do something in thread " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t1([&] {deadLockProcess2(mt1, mt2); });
std::thread t2([&] {deadLockProcess2(mt2, mt1); });
t1.join();
t2.join();
}