3

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

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;

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

4

3 回答 3

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关于此类操作的文章中找到详细说明。

于 2018-12-03T21:59:13.363 回答
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.0100d倍。互斥锁和解锁确保d在给定时间只有一个线程在访问。但是,这比atomic等效的要慢得多,因为锁定和解锁非常昂贵-我听说过基于操作系统和特定处理器以及被锁定或解锁的内容不同的事情,但对于这个例子来说,它在 50 个时钟周期附近,但它可能需要一个更像 2000 个时钟周期的系统调用。

道德:谨慎使用。

于 2018-12-03T20:49:18.870 回答
0

如果你的向量每个线程有很多元素,你应该考虑实现减少而不是对每个元素使用原子操作。原子操作比普通存储要贵得多。

double global_value{0.0};
std::vector<double> private_values(num_threads,0.0);
parallel_for(size_t k=0; k<n; ++k) {
    private_values[my_thread] += ...;
}
if (my_thread==0) {
    for (int t=0; t<num_threads; ++t) {
         global_value += private_values[t];
    }
}

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

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

于 2019-11-23T20:23:19.787 回答