美文网首页
C++11_lock_guard的线程死锁问题和解决

C++11_lock_guard的线程死锁问题和解决

作者: JasonLiThirty | 来源:发表于2020-02-29 16:06 被阅读0次

    视频教程:https://www.bilibili.com/video/av92453755

    std::lock_guard

    • std::lock_guard严格基于作用域(scope-based)的锁管理类模板,构造时是否加锁是可选的(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移,对象生存期内不允许手动加锁和释放锁。
    • 默认构造函数里锁定互斥量,即调用互斥量的lock函数。
    • 析构函数里解锁互斥量,即调用互斥量的unlock函数。
    • std::lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard的生命周期结束之后,它所管理的锁对象会被解锁

    std::unique_lock

    • 与std:::lock_gurad基本一致,但更加灵活的锁管理类模板,构造时是否加锁是可选的,在对象析构时如果持有锁会自动释放锁,所有权可以转移。对象生命期内允许手动加锁和释放锁。但提供了更好的上锁和解锁控制尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码****。
    • std::unique_lock的上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until 和unlock

    两者区别

    • std::unique_lock更灵活,提供了lock, unlock, try_lock等接口
    • std::lock_guard更简单,没有多余的接口,构造函数时拿到锁,析构函数时释放锁,但更省时

    线程死锁

    因为std::lock_guard更简单的特性,所以可能出现下列情况的线程死锁

    #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 参数为 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 参数为 std::defer_lock,表明不lock锁,在执行功能代码之前再统一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();
    }
    

    相关文章

      网友评论

          本文标题:C++11_lock_guard的线程死锁问题和解决

          本文链接:https://www.haomeiwen.com/subject/pfzphhtx.html