避免死锁
文章目录
- 避免死锁
- 总览
- 一把锁
- 单一线程
- std::recursive_mutex
- 标注线程不安全
- 多线程
- 多把锁
- 单一线程
- 多线程
- 上锁顺序相同
- std::lock 同时上锁
总览
解决方法
-
防患未然:没有问题就不需要解决问题。
-
确定使用场景
-
当前资源是否真的存在竞争,是否真的需要使用mutex。
不需要的话就不要用。自然就没有死锁 -
资源的所有权能不能简化,让 每个线程只持有一把锁。
-
-
使用者的RAII错误:从工具上避免
忘记解锁 lock_guard,自由上/解锁unique_lock(更高的开销)
-
-
单一锁
非多线程的问题:单一线程中的 单一锁 多次上锁。
在函数A,函数B同时都需要用一把mutex锁上锁。函数A会调用到函数B
-
会锁死就干脆不要在函数里面用
-
取消各个使用处的这个锁
顾虑就是各个部分单独的使用,也都要加锁。更加的麻烦。
-
写注释告诉使用者
- 标注某个锁是线程不安全的。
- 要求使用者必须在执行前为某个mutex上锁。
-
-
std::recursive_mutex
允许同一线程多次使用一把锁如果同时需要 try_lock_for() 还有 std::recursive_timed_mutex
-
自动判断是不是同一个线程 lock() 了多次同一个锁。
如果是则
-
lock 计数器加1
-
unlock() 会让计数器减1,减到0时才真正解锁。
-
-
相比普通的 std::mutex 有一定性能损失
-
-
-
多把锁
-
单一线程-
永远不要在一个线程中同时持有多个锁 (锁一个,就释放一个)
防患未然:没有问题就不需要解决问题。
-
-
多线程
-
在所有线程中,都按照相同的顺序上锁
-
std::lock std::scoped_lock
直接同时对多个 mutex 上锁,不用关心顺序。
-
操作系统(jvm C#虚拟机)检测死锁。选择性打破。
-
-
一把锁
单一线程
除了多线程相互持有两个锁会造成死锁外,同一线程多次使用一把锁也会死锁
- 只有一个线程一个锁,如果 lock() 以后又调用 lock(),也会造成死锁。
但是有时候,我们确确实实的有如下需求:
如果要求这是一个连续的“原子的”操作,那么在函数A中调用函数B前,解锁mutex,执行结束后再上锁,这个解决方法就是不可行的。
- 在函数A,函数B同时都需要用一把mutex锁上锁。
- 函数A会调用到函数B
std::recursive_mutex
用 std::recursive_mutex
。
优点
-
允许同一线程多次使用一把锁(会帮我们判断是否是同一线程)
-
自动判断是不是同一个线程 lock() 了多次同一个锁,
-
如果是则让计数器加1
-
之后 unlock() 会让计数器减1,减到0时才真正解锁。
-
缺点
- 相比普通的 std::mutex 有一定性能损失
同理还有 std::recursive_timed_mutex,如果你同时需要 try_lock_for() 的话。
标注线程不安全
会锁死就干脆不要在函数里面用这把锁,用在外面。
取消各个使用处的这个锁,写注释告诉使用者:
- 标注某个锁是线程不安全的。
- 要求使用者必须在执行前(一大串函数前)为某个mutex上锁。
顾虑:就是各个部分单独的使用,也都要加锁,非常的麻烦。
std::mutex mtx1;
/// NOTE: please lock mtx1 before calling other()
void other() {// do something
}void func() {mtx1.lock();other();mtx1.unlock();
}int main() {func();return 0;
}
多线程
没问题,一把锁多线程,不存在死锁。
多把锁
单一线程
防患未然:单一线程,永远不要同时持有两个锁 (锁一个,就释放一个)
int main() {std::mutex mtx1;std::mutex mtx2;std::thread t1([&] {for (int i = 0; i < 1000; i++) {mtx1.lock();mtx1.unlock();mtx2.lock();mtx2.unlock();}});std::thread t2([&] {for (int i = 0; i < 1000; i++) {mtx2.lock();mtx2.unlock();mtx1.lock();mtx1.unlock();}});t1.join();t2.join();return 0;
}
多线程
上锁顺序相同
复数个线程之间,由程序员保证多把锁的上锁顺序相同
int main() {std::mutex mtx1;std::mutex mtx2;std::thread t1([&] {for (int i = 0; i < 1000; i++) {mtx1.lock();mtx2.lock();mtx2.unlock();mtx1.unlock();}});std::thread t2([&] {for (int i = 0; i < 1000; i++) {mtx1.lock();mtx2.lock();mtx2.unlock();mtx1.unlock();}});t1.join();t2.join();return 0;
}
std::lock 同时上锁
std::lock 或 std::scoped_lock,支持同时对多把锁上锁
std::lock
可以同时对多个 mutex 上锁,std::scoped_lock
RAII 版本的std::lock。