存在普遍接受的无锁和无等待定义,并且您的问题中提供的定义与这些定义一致。我强烈认为 C++ 标准委员会肯定会坚持科学界普遍接受的定义。
通常,关于无锁/无等待算法的出版物假定 CPU 指令是无等待的。相反,关于进度保证的争论集中在软件算法上。
基于这个假设,我认为任何std::atomic
可以转换为某些架构的单个原子指令的方法在该特定架构上都是无等待的。这种翻译是否可能有时取决于该方法的使用方式。举个例子fetch_or
。在 x86 上,这可以翻译为lock or
,但前提是您不使用它的返回值,因为该指令不提供原始值。如果使用返回值,那么编译器将创建一个 CAS 循环,该循环是无锁的,但不是无等待的。(同样适用于fetch_and
/ fetch_xor
。)
因此,哪些方法实际上是免等待的,不仅取决于编译器,而且主要取决于目标架构。
假设一条指令实际上无需等待在技术上是否正确,恕我直言,这是一个相当哲学的问题。诚然,可能无法保证指令在有限数量的“步骤”内完成执行(无论这样的步骤可能是什么),但机器指令仍然是我们可以看到和控制的最低级别的最小单元。实际上,如果您不能假设一条指令是无等待的,那么严格来说,不可能在该架构上运行任何实时代码,因为实时还需要严格限制时间/步数。
这就是 C++17 标准中的规定[intro.progress]
:
定义为无锁(32.8)或指示为无锁(32.5)的原子函数的执行是无锁执行。
- 如果标准库函数中只有一个线程未被阻塞(3.6),则该线程中的无锁执行将完成。[注意:并发执行的线程可能会阻止无锁执行的进度。例如,这种情况可能发生在负载锁定的存储条件实现中。这种特性有时被称为无障碍。——尾注]
- 当一个或多个无锁执行同时运行时,至少应该完成一个。[注意:对于某些实现来说,很难对此效果提供绝对保证,因为来自其他线程的重复且特别不合时宜的干扰可能会阻止向前进展,例如,通过在负载锁定和存储条件之间重复窃取缓存行以实现不相关的目的指示。实现应确保此类影响不会无限期地延迟预期操作条件下的进度,因此程序员可以安全地忽略此类异常。在本文档之外,此属性有时称为无锁属性。——尾注]
另一个答案正确地指出我最初的答案有点不精确,因为存在两种更强的等待自由子类型。
- wait-free - 如果方法保证每次调用在有限步数内完成其执行,则该方法是无等待的,即不可能确定上限,但仍必须保证步数为有限的。
- 无等待有界- 如果方法保证每个调用在有界数量的步骤中完成其执行,则该方法是无等待有界的,其中该边界可能取决于线程数。
- wait-free population oblivious - 如果一个方法保证每个调用都在有限的步数内完成其执行,并且此界限不依赖于线程数,则该方法是无等待人口遗忘的。
所以严格来说,问题中的定义和wait-free bounded的定义是一致的。
在实践中,大多数无等待算法实际上是无等待有界甚至无等待种群无意识的,即可以确定步数的上限。