我试图了解C11内存模型的工作原理,并编写了两个包含conflict
(在5.1.2.4(p4)
的意义上)的表达式的函数:
struct my_struct{
uint64_t first;
int64_t second;
} * _Atomic instance;
void* set_first(void *ignored){
uint64_t i = 0;
while(1){
struct my_struct *ms = atomic_load_explicit(&instance, memory_order_acquire);
ms -> first = i++;
atomic_store_explicit(&instance, ms, memory_order_release);
sleep(1);
}
}
void* print_first(void *ignored){
while(1){
struct my_struct *ms = atomic_load_explicit(&instance, memory_order_acquire);
uint64_t current = ms -> first;
char buf[100];
memset(buf, '\0', sizeof(buf));
sprintf(buf, "%" PRIu64 "\n", current);
fputs_unlocked(buf, stdout);
sleep(2);
}
}
主要功能:
int main(void){
struct my_struct tmp = {.first = 0, .second = 0};
atomic_init(&instance, &tmp);
printf("main\n");
pthread_t set_thread;
pthread_create(&set_thread, NULL, &set_first, NULL);
pthread_t print_thread;
pthread_create(&print_thread, NULL, &print_first, NULL);
while(1){
sleep(100);
}
}
所以我试图证明程序是否包含没有数据争用。这是我的想法:
我们知道,对原子对象的释放操作与对该对象的获取操作同步。因此,atomic_store_explicit(&instance, ms, memory_order_release);
中的set_first
与atomic_load_explicit(&instance, memory_order_acquire)
中的{em> print_first
同步。
由于ms -> first = i++
函数中的副作用set_first
出现在程序文本中的atomic_store_explicit(&instance, ms, memory_order_release);
之前,因此我认为它是 先于顺序 (我不确定,找不到任何规范性引用)。
将项目符号1.
和2.
组合起来意味着ms -> first = i++
线程间线程发生在 之前atomic_load_explicit(&instance, memory_order_acquire);
因此它们处于 发生 关系之前。
应用数据争用定义,我们得出结论,uint64_t current = ms -> first;
函数中的约束动作print_first
和ms -> first = i++;
函数中的set_first
约束不会产生数据争用
所以行为似乎是明确定义的。
可疑的事情是假设ms -> first = i++;
在 atomic_store_explicit(&instance, ms, memory_order_release);
之前排序,因为它们在程序文本中先后出现。
是正确的还是程序包含数据争用?
答案 0 :(得分:3)
通过非_Atomic
指针修改非原子对象不是固有数据争用UB。 (例如,您可以使用一种算法int *p = shared_ptr++;
来使每个线程在非原子阵列中抓住自己的插槽。)
但是在这种情况下,您有一个明确的UB案例,因为您有2个线程访问main的tmp.first
,而且它们都不都是读的。
具有mo_release
的商店在任何先前的商店(和装载)之后都被排序,包括诸如ms->first = ...
之类的非原子商店。这就是发行商店与放松的重点。
但是您推理的缺陷在于步骤1:atomic_store_explicit(&instance, ms, memory_order_release)
中的set_first
只有与同步才能获取看到存储的值的负载!获取其他线程中的负载不会神奇地等待尚未发生的发布存储。可以保证的是,如果/当您执行加载由发布存储区 1 存储的值时,您还将看到该线程中的所有较早版本。
如果获取负载发生在发布存储之前(按照全局顺序,如果有的话),那么就没有同步。
两个线程中的获取负载可以同时发生,然后狐狸出现在鸡舍中:ms -> first = i++;
和uint64_t current = ms -> first;
在没有同步的情况下运行。
完全无关紧要的是,写入线程稍后将进行释放存储以将相同的值存储回instance
中。
脚注1: (标准中的“发布顺序”语言将其扩展到查看修改初始发布存储结果的RMW操作的结果,等等。)
就其他线程而言,atomic_load_explicit
中的set_first
基本上是无关紧要的。您不妨将其吊起。