我找不到基于锁的原子和无锁的原子之间的语义差异。据我所知,就语言而言,这种差异在语义上是无意义的,因为语言不提供任何时间保证。我能找到的唯一保证是内存排序保证,这两种情况似乎是相同的。
原子的无锁性如何影响程序语义?
。,除了调用is_lock_free
或atomic_is_lock_free
之外,是否有可能编写一个定义良好的程序,其行为实际上受到原子是否无锁的影响?
这些函数有语义意义吗?或者它们只是编写响应式程序的实用技巧,即使语言从一开始就没有提供时间保证?
至少有一个语义差异。
As per C++11 1.9 Program execution /6
:
当抽象机的处理被接收到一个信号而中断时,在信号处理程序的执行过程中,既不是
volatile std::sig_atomic_t
类型的对象,也不是无锁原子对象的值都不指定,任何不属于这两种类型的对象的值都不指定处理程序修改的这两个类别变为未定义。
换句话说,处理这两类变量是安全的,但应该避免访问或修改所有其他类别的变量。
当然,你可以争辩说,如果你调用未指定/未定义的行为,它不再是一个良好的定义的程序,但我不完全确定你是指这还是良好的(即,可编译的)。
但是,即使您忽略了语义差异,性能差异也值得拥有。如果我必须有一个线程间通信的值,我可能会选择,按照优先顺序:
- 没有锁的最小数据类型。
- 大于必要的数据类型,如果它是无锁的,而较小的数据类型不是。
- 一个完全支持竞争条件的共享区域,但与
atomic_flag
(保证无锁)一起控制访问。
这种行为可以在编译时或运行时根据ATOMIC_x_LOCK_FREE
宏选择,这样,即使程序的行为是相同的,也会为该行为选择最佳的方法。
在c++ 11标准中,术语"无锁"没有像LWG #2075中报告的那样得到很好的定义。
c++ 14标准定义了c++语言中无锁执行是什么(N3927批准)。
引用c++ 14 1.10[引言]。多流)/段4:
定义为无锁(29.7)或指示为无锁(29.4)的原子函数的执行为无锁执行。
- 如果只有一个未阻塞的线程,则该线程中的无锁执行将完成。注意:并发执行的线程可能会阻止无锁执行的进程。例如,这种情况可能发生在负载锁定的存储条件实现中。这种特性有时被称为无阻塞。
- 当一个或多个无锁执行并发运行时,至少应该完成一个。[注:有些实现很难提供绝对保证这种效果,因为来自其他线程的重复和特别不恰当的干扰可能会阻止前进的进程,例如,在负载锁定指令和存储条件指令之间为了不相关的目的反复窃取缓存行。]实现应该确保这样的影响在预期的操作条件下不会无限期地延迟进程,并且这样的异常可以被程序员安全地忽略。在本国际标准之外,这种特性有时被称为无锁。
以上"无锁"的定义取决于未阻塞线程的行为。c++标准没有直接定义未阻塞线程,但是17.3.3[defns. net]。Blocked]定义了阻塞的线程:
正在等待某些条件(而不是处理器的可用性)满足后才能继续执行的线程
原子的无锁性如何影响程序语义?
我认为答案是否定的,除了信号处理程序作为paxdiablo的答案,当"程序语义"意味着原子操作的副作用。原子的锁自由度影响整个多线程程序的进程保证的强度。当两个(或多个)线程并发地对同一对象执行无锁原子操作时,这些操作中至少有一个应该在任何最差的线程调度下完成。换句话说,理论上,"邪恶的"线程调度程序可以故意阻塞基于锁的原子操作的进程。
Paxdiablo回答得很好,但一些背景知识可能会有所帮助。
"无锁原子"是一个有点多余的术语。原子变量的意义,正如它们最初被发明的那样,是通过利用硬件保证来避免锁。但是,每个平台都有自己的局限性,而c++具有很高的可移植性。因此,实现必须模拟原子性(通常通过库),对硬件级别上并不存在的原子类型使用细粒度锁。
硬件原子和"软件原子"之间的行为差异被最小化,因为差异将意味着失去可移植性。另一方面,程序应该能够避免意外使用互斥锁,因此可以通过预处理器可用的ATOMIC_x_LOCK_FREE
进行自省。