为避免死锁,常用的建议是始终使用相同的顺序锁定两个互斥元,但也有例外情况,如两个线程尝试通过交换参数而在相同的两个实例之间交换数据,将产生死锁。
幸运的是,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
提供了关于锁定给定的互斥元的全或无的语义。
避免死锁的一些办法:
- 避免嵌套锁:已经持有一个锁,就别再获取锁
- 在持有锁时,避免调用用户提供的代码
- 以固定顺序获取锁
- 使用锁层次:在高层锁定低层互斥元;如果在较低层已经持有锁定,则不允许锁定该互斥元