为避免死锁,常用的建议是始终使用相同的顺序锁定两个互斥元,但也有例外情况,如两个线程尝试通过交换参数而在相同的两个实例之间交换数据,将产生死锁。
幸运的是,C++标准库中的 std::lock 可以解决这一问题——std::lock 函数可以同时锁定两个或更多的互斥元,而没有死锁的风险。
1 | class BigObject; |
std::lock_guard 额外提供一个参数 std::adopt_lock 给互斥元,告知 std:lock_guard 对象该互斥元已被锁定,并且它们只应沿用互斥元上已有的锁的所有权,而不是试图在构造函数中锁定互斥元。
std:unique_lock 保留互斥元为未锁定,但占用更多空间并且使用起来比 std::lock_guard 略慢。
这就确保了通常在受保护的操作可能引发异常的情况下,函数退出时正确地解锁互斥元,这也考虑到了简单返回。此外,在对 std::lock 的调用中锁定 lhs.m 抑或是 rhs.m 都可能引发异常,在这种情况下,该异常被传播出 std::lock,如果 std::lock 已经成功地在一个互斥元上获取了锁,当它试图在另一个互斥元上获取锁的时候,就会引发异常,前一个互斥元将会自动释放。std::lock 提供了关于锁定给定的互斥元的全或无的语义。
避免死锁的一些办法:
- 避免嵌套锁:已经持有一个锁,就别再获取锁
- 在持有锁时,避免调用用户提供的代码
- 以固定顺序获取锁
- 使用锁层次:在高层锁定低层互斥元;如果在较低层已经持有锁定,则不允许锁定该互斥元