C++11_lock_guard的线程死锁问题和解决
2020-02-29 本文已影响0人
JasonLiThirty
视频教程: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();
}