2

我试图了解 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);
    }
}

所以我试图证明程序是否不包含 data-races。以下是我的想法:

  1. 我们知道原子对象上的释放操作与对象上的获取操作同步。所以atomic_store_explicit(&instance, ms, memory_order_release);在 中与中set_first 同步atomic_load_explicit(&instance, memory_order_acquire)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);因为它们在程序文本中一个接一个地发生。

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

4

1 回答 1

3

通过非指针修改非原子对象本质_Atomic上不是数据竞争 UB。(例如,您可以有一个算法让每个线程在非原子数组中获取自己的插槽。)int *p = shared_ptr++;

但是在这种情况下,您有一个明确的 UB 案例,因为您有 2 个线程访问 main's tmp.first,并且它们不是都读取。


存储mo_release在任何先前存储(和加载)之后排序,包括非原子存储,如ms->first = .... 这就是发布商店与放松的重点。

但是您的推理中的缺陷在于第 1 步:atomic_store_explicit(&instance, ms, memory_order_release)同步获取负载中看到存储的值! 在其他线程中获取负载不会神奇地等待尚未发生的发布存储。保证是,如果/当您加载由 release store 1存储的值时,您还会看到该线程的所有早期内容。set_first

如果获取加载发生在发布存储之前(按照全局顺序,如果有的话),那么就没有与之同步。

两个线程中的获取加载可以同时发生,然后狐狸在鸡舍中:ms -> first = i++;并且uint64_t current = ms -> first;没有同步地运行。

编写线程稍后将执行发布存储以将相同的值存储回instance.


脚注 1:(标准中的“发布序列”语言将此扩展为查看修改初始发布存储结果的 RMW 操作的结果,依此类推。)


就其他线程而言,atomic_load_explicitinset_first基本上是无关紧要的。您不妨将其吊出循环。

于 2019-04-13T13:20:05.163 回答