fetch_add使用acq_rel内存顺序

时间:2016-11-17 07:35:25

标签: c++ atomic memory-barriers

考虑一下

std::atomic<int> x(0);

假设我有一个函数执行以下操作:

int x_old = x.fetch_add(1,std::memory_order_acq_rel);

基于description for acquire release memory ordering

  

memory_order_relaxed 放松操作:没有同步或排序约束,此操作只需要原子性(请参阅下面的放松排序)

     

memory_order_consume 具有此内存顺序的加载操作会对受影响的内存位置执行使用操作:在此加载之前,可以对当前线程中的取决于当前加载的值的读取或写入进行重新排序。在当前线程中可以看到对释放相同原子变量的其他线程中的数据相关变量的写入。在大多数平台上,这仅影响编译器优化(请参阅下面的Release-Consume排序)

     

memory_order_acquire 具有此内存顺序的加载操作会对受影响的内存位置执行获取操作:在此加载之前,不能对当前线程中的读取或写入进行重新排序。在当前线程中可以看到释放相同原子变量的其他线程中的所有写入(请参阅下面的发布 - 获取顺序)

     

memory_order_release 具有此内存顺序的存储操作会执行释放操作:在此存储之后,不能对当前线程中的读取或写入进行重新排序。当前线程中的所有写入在获取相同原子变量的其他线程中都是可见的(请参阅下面的释放 - 获取排序),并且在原子变量中携带依赖关系的写入在消耗相同原子的其他线程中变得可见(请参阅Release-Consume订购如下)。

     

memory_order_acq_rel 具有此内存顺序的读取 - 修改 - 写入操作既是获取操作又是释放操作。当前线程中的内存读取或写入不能在此存储之前或之后重新排序。在修改之前,其他发布相同原子变量的线程中的所有写入都是可见的,并且修改在获取相同原子变量的其他线程中可见。

     

memory_order_seq_cst 具有此内存顺序的任何操作都是获取操作和释放操作,并且存在单个总顺序,其中所有线程以相同顺序观察所有修改(请参阅顺序一致排序)下面)

2个不同的线程是否可以接收相同的x_old值0?或者他们保证以x_old仅为其中一个为0的方式执行,而另一个为1。

如果两个x_old都为0,那么将内存排序更改为std::memory_order_seq_cst是否保证x_old的唯一性?

2 个答案:

答案 0 :(得分:7)

  

2个不同的线程是否可以接收相同的x_old值0?

这是不可能的,因为操作是 atomic 。它要么全部发生,要么根本不发生。

订购涉及前/后加载/存储,因为您没有,订购与此无关。换句话说,x.fetch_add(1, std::memory_order_relaxed);在这里具有相同的效果。

在当前x86上,无论lock xadd是什么memory_order指令,lock前缀都提供原子性和排序。对于memory_order_relaxedlock的排序部分是不必要的。

答案 1 :(得分:0)

任何在内存上执行的操作都在处理器内部完成。即使这是原子操作,处理器也会读取,修改和写回新值。如果操作失败(取决于实现,它可能无法失败,而是阻止),它会重复自身。如果成功,为了使操作正确,新值必须是紧接的先前值,并根据请求进行修改然后存储。修改后的值将返回给用户。处理器没有理由再次从内存中读取并从随机时间返回该值。如果返回的值不是前一个值,则结果操作将不正确。

您可以使用以下方法进行测试:

long repeats = 1000000000;
long x = 0;
long sum = 0;
void *test_func(void*arg){
    long local_sum = 0;
    for (int i = 0; i < repeats; ++i) {
        local_sum += atomic_fetch_add_explicit(&x, 1, memory_order_relaxed);
    }
    atomic_fetch_add(&sum, local_sum);
    return NULL;
}

如果结果与顺序执行相同,则一切正常。

    long correct_res = 0;
    for (int i = 0; i < repeats * no_threads; ++i) {
        correct_res = correct_res + i;
    }

以及完整的代码:

#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>

long repeats = 1000000000;
long x = 0;
long sum = 0;
void *test_func(void*arg){
    long local_sum = 0;
    for (int i = 0; i < repeats; ++i) {
        local_sum += atomic_fetch_add_explicit(&x, 1, memory_order_relaxed);
    }
    atomic_fetch_add(&sum, local_sum);
    return NULL;
}

int main() {
    long correct_res = 0;
    for (int i = 0; i < repeats * 2; ++i) {
        correct_res = correct_res + i;
    }
    pthread_t pthread[2];
    pthread_create(&pthread[0], NULL, test_func, NULL);
    pthread_create(&pthread[1], NULL, test_func, NULL);

    pthread_join(pthread[0], NULL);
    pthread_join(pthread[1], NULL);
    printf("correct res : %ld\n res : %ld\n", correct_res, sum);
    if(correct_res == sum)
        printf("Success.\n");
    else
        printf("Failure.\n");
    return 0;
}