应该std::recursive_mutex
是默认的并被std::mutex
视为性能优化?
不是真的,不。使用非递归锁的优点不仅仅是性能优化,它意味着你的代码可以自我检查叶级原子操作是否真的是叶级的,它们不会调用使用锁的其他东西。
有一种相当常见的情况:
- 实现一些需要序列化的操作的函数,因此它需要互斥体并执行此操作。
- 另一个实现更大的序列化操作的函数,并且想要调用第一个函数来执行它的一个步骤,同时它为更大的操作持有锁。
为了一个具体的例子,也许第一个函数从列表中原子地删除一个节点,而第二个函数从一个列表中原子地删除两个节点(并且您永远不希望另一个线程看到仅使用两个节点中的一个的列表出去)。
为此,您不需要递归互斥锁。例如,您可以将第一个函数重构为一个公共函数,该函数获取锁并调用一个“不安全”执行操作的私有函数。然后第二个函数可以调用相同的私有函数。
但是,有时使用递归互斥锁会很方便。这种设计仍然存在一个问题:在类不变量不成立的地方remove_two_nodes
调用remove_one_node
(第二次调用它时,列表正好处于我们不想公开的状态)。但是假设我们知道它remove_one_node
不依赖于那个不变量,这不是设计中的致命错误,只是我们使我们的规则比理想的“所有类不变量总是在任何公共函数时都成立”更复杂一点。进入”。
所以,这个技巧有时很有用,我并不像文章那样讨厌递归互斥锁。我没有历史知识来争论将它们包含在 Posix 中的原因与文章所说的不同,“展示互斥体属性和线程扩展”。不过,我当然不认为它们是默认值。
我认为可以肯定地说,如果在您的设计中您不确定是否需要递归锁,那么您的设计就是不完整的。稍后您会后悔您正在编写代码并且您不知道是否允许已经持有锁这样的根本重要的事情。所以不要“以防万一”放置递归锁。
如果你知道你需要一个,就用一个。如果您知道自己不需要,那么使用非递归锁不仅仅是一种优化,它还有助于强制执行设计约束。第二把锁失败比它成功并隐藏你不小心做了一些你的设计认为不应该发生的事情更有用。但是,如果您遵循您的设计,并且从不双重锁定互斥锁,那么您将永远无法确定它是否是递归的,因此递归互斥锁不会直接有害。
这个类比可能会失败,但这是另一种看待它的方式。想象一下,您可以在两种指针之间进行选择:一种在您取消引用空指针时使用堆栈跟踪中止程序,另一种返回0
(或将其扩展为更多类型:表现得好像指针引用了一个值-初始化对象)。非递归互斥锁有点像中止的互斥锁,递归互斥锁有点像返回 0 的互斥锁。它们都有各自的用途——人们有时会竭尽全力实现“安静的非-值”值。但是,如果您的代码设计为从不取消引用空指针,则默认情况下您不想使用静默允许这种情况发生的版本。