在并行循环中添加double-std :: atomic <double>

时间:2018-12-03 18:38:08

标签: c++ multithreading concurrency atomic parallel-for

我有一个并行代码,该代码进行一些计算,然后将double添加到循环外double变量中。我尝试使用std :: atomic,但对std :: atomic 变量的算术运算没有支持。

double dResCross = 0.0;
std::atomic<double> dResCrossAT = 0.0;

Concurrency::parallel_for(0, iExperimentalVectorLength, [&](size_t m)
{
     double value;
     //some computation of the double value
     atomic_fetch_add(&dResCrossAT, value);
});
dResCross += dResCrossAT;

简单地写

dResCross += value;

显然是胡说八道。我的问题是,如何在不使代码串行的情况下解决此问题?

3 个答案:

答案 0 :(得分:3)

原子地对浮点类型执行算术运算的典型方法是使用比较交换(CAS)循环。

 double value;
 //some computation of the double value

 double expected = atomic_load(&dResCrossAT);

 while (!atomic_compare_exchange_weak(&dResCrossAT, &expected, expected + value));

可以在Jeff Preshing's article中找到有关此类操作的详细说明。

答案 1 :(得分:0)

我相信,排除非原子变量中的部分内存写入需要进行互斥,我不确定这是否是确保没有写入冲突的唯一方法,但可以做到这一点

#include <mutex>
#include <thread>

std::mutex mtx;

void threadFunction(double* d){
    while (*d < 100) {
        mtx.lock();
        *d += 1.0;
        mtx.unlock();
    }
}

int main() {
    double* d = new double(0);
    std::thread thread(threadFunction, d);
    while (true) {
        if (*d == 100) {
            break;
        }
    }
    thread.join();
}

这将以线程安全的方式将1.0添加到d 100次。互斥锁锁定和解锁可确保在给定时间只有一个线程正在访问d。但是,这比atomic慢得多,因为锁定和解锁非常昂贵-我听说过根据操作系统和特定处理器以及锁定或解锁的内容而有所不同,但大约在50时钟附近在此示例中,系统需要一个周期,但它可能需要一个系统调用,该调用更像是2000个时钟周期。

道德:请谨慎使用。

答案 2 :(得分:0)

如果向量每个线程中有许多元素,则应考虑实现简化,而不是对每个元素使用原子操作。原子操作比普通存储要昂贵得多。

(char*)x+1*(3*4*5)+1*(4*5)

此算法不需要原子操作,在许多情况下会更快。如果线程计数很高(例如在GPU上),则可以用树或原子替换第二阶段。

TBB和Kokkos等并发库都提供并行的reduce模板,这些模板在内部做正确的事情。