通过 _Atomic 指针修改非原子结构是否会产生数据争用?
Does Non-atomic struct modification via _Atomic pointer produce data-race?
我试图了解 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, '[=10=]', 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);
}
}
所以我试图证明程序是否包含没有数据竞争。以下是我的想法:
我们知道原子对象的释放操作与对象的获取操作是同步的。因此 set_first
中的 atomic_store_explicit(&instance, ms, memory_order_release);
与 print_first
中的 atomic_load_explicit(&instance, memory_order_acquire)
同步。
由于set_first
函数中的副作用ms -> first = i++
出现在程序文本中的atomic_store_explicit(&instance, ms, memory_order_release);
之前,我假设它是sequenced-before吧(这个我不太清楚,没找到规范的参考)。
组合项目符号 1.
和 2.
意味着 ms -> first = i++
线程间发生在 之前atomic_load_explicit(&instance, memory_order_acquire);
所以他们在之前发生关系。
应用数据竞争定义,我们得出结论,print_first
函数中的冲突操作 uint64_t current = ms -> first;
和 set_first
函数中的 ms -> first = i++;
不会产生数据竞争。
所以行为似乎是明确定义的。
可疑的事情假设ms -> first = i++;
顺序在atomic_store_explicit(&instance, ms, memory_order_release);
之前,因为它们一个接一个地发生在程序文本。
是否正确或程序包含数据竞争?
通过非_Atomic
指针修改非原子对象不是固有的 数据争用 UB。 (例如,您可以使用一种算法 int *p = shared_ptr++;
让每个线程在非原子数组中获取自己的插槽。)
但是在这个案例中,你有一个明显的UB案例,因为你有2个线程访问main的tmp.first
,而且他们不是都读。
具有 mo_release
的存储在任何先前的存储(和加载)之后排序,包括像 ms->first = ...
这样的非原子存储。这就是 release-stores 与 relaxed 的区别。
但是您推理中的缺陷在步骤 1 中:atomic_store_explicit(&instance, ms, memory_order_release)
在 set_first
中仅 与 同步获取负载存储的值! 在其他线程中获取负载 不要神奇地等待尚未发生的释放存储。保证是 if/when 你 做 加载发布存储 1 存储的值,你还可以从该线程看到所有早期的东西.
如果获取加载发生在发布存储之前(在全局顺序中,如果有的话),则不会与之同步。
两个线程中的acquire-load可以同时发生,然后狐狸在鸡舍:ms -> first = i++;
和uint64_t current = ms -> first;
是运行 没有同步。
写入线程稍后将执行释放存储以将相同的值存储回 instance
。
完全无关紧要
脚注 1:
(标准中的 "release-sequence" 语言将此扩展为查看修改初始发布存储结果的 RMW 操作的结果,等等。)
对于其他线程而言,set_first
中的atomic_load_explicit
基本无关紧要。您不妨将其吊出循环。
我试图了解 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, '[=10=]', 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);
}
}
所以我试图证明程序是否包含没有数据竞争。以下是我的想法:
我们知道原子对象的释放操作与对象的获取操作是同步的。因此
set_first
中的atomic_store_explicit(&instance, ms, memory_order_release);
与print_first
中的atomic_load_explicit(&instance, memory_order_acquire)
同步。由于
set_first
函数中的副作用ms -> first = i++
出现在程序文本中的atomic_store_explicit(&instance, ms, memory_order_release);
之前,我假设它是sequenced-before吧(这个我不太清楚,没找到规范的参考)。组合项目符号
1.
和2.
意味着ms -> first = i++
线程间发生在 之前atomic_load_explicit(&instance, memory_order_acquire);
所以他们在之前发生关系。应用数据竞争定义,我们得出结论,
print_first
函数中的冲突操作uint64_t current = ms -> first;
和set_first
函数中的ms -> first = i++;
不会产生数据竞争。
所以行为似乎是明确定义的。
可疑的事情假设ms -> first = i++;
顺序在atomic_store_explicit(&instance, ms, memory_order_release);
之前,因为它们一个接一个地发生在程序文本。
是否正确或程序包含数据竞争?
通过非_Atomic
指针修改非原子对象不是固有的 数据争用 UB。 (例如,您可以使用一种算法 int *p = shared_ptr++;
让每个线程在非原子数组中获取自己的插槽。)
但是在这个案例中,你有一个明显的UB案例,因为你有2个线程访问main的tmp.first
,而且他们不是都读。
具有 mo_release
的存储在任何先前的存储(和加载)之后排序,包括像 ms->first = ...
这样的非原子存储。这就是 release-stores 与 relaxed 的区别。
但是您推理中的缺陷在步骤 1 中:atomic_store_explicit(&instance, ms, memory_order_release)
在 set_first
中仅 与 同步获取负载存储的值! 在其他线程中获取负载 不要神奇地等待尚未发生的释放存储。保证是 if/when 你 做 加载发布存储 1 存储的值,你还可以从该线程看到所有早期的东西.
如果获取加载发生在发布存储之前(在全局顺序中,如果有的话),则不会与之同步。
两个线程中的acquire-load可以同时发生,然后狐狸在鸡舍:ms -> first = i++;
和uint64_t current = ms -> first;
是运行 没有同步。
写入线程稍后将执行释放存储以将相同的值存储回 instance
。
脚注 1: (标准中的 "release-sequence" 语言将此扩展为查看修改初始发布存储结果的 RMW 操作的结果,等等。)
对于其他线程而言,set_first
中的atomic_load_explicit
基本无关紧要。您不妨将其吊出循环。