std::scoped_lock 单个互斥锁的行为

std::scoped_lock behaviour with a single mutex

上下文:

我知道自从 std::scoped_lock 一起出现后,std::lock_guard 有点被弃用了。

我也知道 std::scoped_lock 是首选,因为它可以处理多个互斥锁并使用与 std::lock 相同的死锁避免算法。

我对只有一个互斥锁的情况感兴趣,因此我们不需要关心死锁避免。

我从 那里读到:

You can consider std::lock_guard deprecated. The single argument case of std::scoped_lock can be implemented as a specialization and such you don't have to fear about possible performance issues.

问题:

我想知道这句话有多少是真的。

我的意思是,是否(根据标准)保证使用单个互斥锁,std::scoped_lock 将被专门化,以便它可以消除由于死锁避免处理而导致的任何不必要的开销?


我的想法:

经过对问题的一些调查,我从cppreference中找到了以下句子:

If several mutexes are given, deadlock avoidance algorithm is used as if by std::lock.

这可以让我们推断出这样的事情不会发生,否则(即如果只给出一个互斥量)。
但再次重申,这只是一个假设。

从这个 c++ draft 我没有看到任何关于这种专业化的明确提及。
我得到的唯一一句话是:

When sizeof...(MutexTypes) is 1, the supplied Mutex type shall meet the Cpp17BasicLockable requirements. Otherwise, each of the mutex types shall meet the Cpp17Lockable requirements.

(强调我的)

我知道 BasicLockable 要求强制存在 lock()unlock() 函数,这些函数满足定义的 here 等条件。
另一方面,Lockable 要求假设 BasicLockable 要求加上 try_lock() 满足定义等条件的功能there.

我知道 try_lock() 函数是 运行 std::lock 使用的死锁避免算法所必需的。

根据上述 摘录草案所述,如果我们只给 std::scoped_lock.
一个互斥锁,则不需要 try_lock() 函数 这足以 deduce/consider 始终定义上述专业化(并且可能表现得像 std::lock_guard 会做的那样)。
我会说是的,但因为我从未看到任何明确提及它,我想知道我是否正确或者我是否遗漏了什么?


编辑:

我刚刚注意到我错过了最重要的部分 here,其中指出:

Effects: Initializes pm with tie(m...). Then if sizeof...(MutexTypes) is 0, no effects. Otherwise if sizeof...(MutexTypes) is 1, then m.lock(). Otherwise, lock(m...).

(强调我的)

这回答了我的疑问,std::lock 仅在存在多个给定互斥体时才被调用。我应该在问问题之前看到它...

当只提供一个互斥量时,

std::scoped_lock 的行为必须与 std::lock_guard 相同。因此,对单个互斥情况的不同要求。

这可以通过专门化来完成,也可以通过不同的内部机制来完成,只要行为相同即可。

如果你读过 lock_guard 的规范(就在 scoped_lock 的正上方)应该就清楚了。

[thread.lock.guard]-3

Initializes pm with m. Calls m.lock()

[thread.lock.scoped]-3

Initializes pm with tie(m...). [...] Otherwise if sizeof...(MutexTypes) is 1, then m.lock(). [...]

它没有明确提到使用 lock_guard 但它必须具有相同的行为。

该标准很少会通过专门化来保证某种优化(著名的例子是对不同迭代器类型 a 的专门化和可憎的 std::vector<bool>)。为此,有两种方法可以解决:

  1. 相信您的 compiler/standard 库实现。编译器是史诗般的,它们进行极其高级的优化,其中一些是您梦寐以求的。 STL 的实现在大多数情况下都很棒。有些地方它们速度较慢,因为它们必须能够处理奇怪的边缘情况,但这里必须已经有不同的专业化,因为单参数情况只需要 BasicLockable,所以它将有一个不需要的实现try_lock,为什么不应该是高效的。
  2. 执行你的代码。测试它是否足够快,测试 scoped_lock 是否在你代码的热路径上,如果你真的认为(并且有数据证明)scoped_lock 很慢,然后才替换它使用 lock_guard 并再次测试。