C++11_lock_guard的線程死鎖問題和解決

視頻教程: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();
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容