考虑以下测试程序,该程序在完全实现 C2011 原子和线程的实现上编译和运行。
#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>
#if !ATOMIC_INT_LOCK_FREE
#error "Program behavior is only interesting if atomic_int is lock-free."
#endif
static atomic_int var;
int inc_print(void *unused)
{
atomic_fetch_add(&var, 1);
printf(" %d", atomic_load(&var));
return 0;
}
int main(void)
{
thrd_t thrd;
if (thrd_create(&thrd, inc_print, 0) == thrd_success) {
inc_print(0);
thrd_join(&thrd, 0);
}
putchar('\n');
return 0;
}
我已经设法说服自己以下所有陈述都是正确的:
- 每个线程的
atomic_load
必须观察同一线程执行的增量,因此它无法读取零。
- 每个线程的
atomic_load
可能会也可能不会观察到另一个线程执行的增量。 (另一个线程可能在atomic_load之后才被调度。)因此,它可以读取1或2。
- 致电
printf
已连载only互相对抗。因此,如果一个线程atomic_load
读取 1 和另一个线程的atomic_load
读取 2,或者1 2
or 2 1
可以打印。
- 两者皆有可能
atomic_load
s 观察另一个线程执行的增量,因此输出2 2
也是可以的。
但我不确定的是:是否有可能neither of the atomic_load
s 观察另一个线程执行的增量?也就是说,是输出1 1
可能的?
另外,放松记忆模型会改变什么吗?
你的结论对我来说看起来是正确的。
默认memory_order_seq_cst
保证整个程序执行以顺序一致的方式 https://en.wikipedia.org/wiki/Race_condition#SC_for_DRF,因为它不存在数据争用并且不使用任何非 SC 原子。所以可能的结果只是程序顺序的交错。
这允许两个增量然后两个加载,但一个增量必须在另一个增量之后,看看它的1
结果并写一个2
。并且该线程中的负载必须在它之后,所以至少有一个线程看到2
. The 1 1
结果是不可能的,2 2
结果可能会发生。
宽松原子不会在这里引入任何新的可能性;我们可以从操作规则中获得同样的保证same无论 memory_order 参数如何,都适用的原子变量。
- 一致的修改顺序
var
存在,并且总共两个原子增量必须完成总共+=2
。 (出于这个原因,原子 RMW 保证读取最新值,以确保同一对象上的 RMW 彼此序列化,而不是同时加载 0 和写入 1。这不会是原子增量。)
- A
load
之后fetch_add
在同一线程中必须看到其结果或修改顺序中的某个后续值。 (在 C++ 中这是读写一致性保证 https://eel.is/c++draft/intro.races#note-19,以及sequenced-before(在同一线程中)强制在两个操作之间进行排序same原子对象。欢迎编辑并提供 C11 标准中等效语言的链接。)
是的,printf
s 的排序独立于原子修改var
。它有效地锁定stdout
。如果这像 C11 的规则一样有效mtx_lock
,这就像一个acquire
对互斥锁进行操作,因此它可以在增量或加载完成之前开始获取锁定。
并不是说那是相关的;这不是必需的2 1
输出成为可能。即使锁定是seq_cst
,您可能会进入这样一种状态:两个 printf 都没有开始,但所有原子都已完成。一个线程的临时值是 1,另一个线程的临时值是 2。那么首先打印的只是机会。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)