通过_Atomic指针进行的非原子结构修改是否会产生数据争用?

时间:2019-04-13 08:13:02

标签: c multithreading concurrency stdatomic 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, '\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);
    }
}

所以我试图证明程序是否包含没有数据争用。这是我的想法:

  1. 我们知道,对原子对象的释放操作与对该对象的获取操作同步。因此,atomic_store_explicit(&instance, ms, memory_order_release);中的set_firstatomic_load_explicit(&instance, memory_order_acquire)中的{em> print_first同步。

  2. 由于ms -> first = i++函数中的副作用set_first出现在程序文本中的atomic_store_explicit(&instance, ms, memory_order_release);之前,因此我认为它是 先于顺序 (我不确定,找不到任何规范性引用)。

  3. 将项目符号1.2.组合起来意味着ms -> first = i++ 线程间线程发生在 之前atomic_load_explicit(&instance, memory_order_acquire);因此它们处于 发生 关系之前。

  4. 应用数据争用定义,我们得出结论,uint64_t current = ms -> first;函数中的约束动作print_firstms -> first = i++;函数中的set_first约束不会产生数据争用

所以行为似乎是明确定义的。

可疑的事情是假设ms -> first = i++; atomic_store_explicit(&instance, ms, memory_order_release);之前排序,因为它们在程序文本中先后出现。

是正确的还是程序包含数据争用?

1 个答案:

答案 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基本上是无关紧要的。您不妨将其吊起。