如果其他线程只读取共享数据,是否需要 OpenMP 原子写入?

Is OpenMP atomic write needed if other threads read only the shared data?

我在 C++ 中有一个 openmp 并行循环,其中所有线程都访问一个共享的双精度数组。

我是否需要原子写入来确保读取的数据有效(旧的或更新的),或者只有当多个线程试图在同一位置写入时才需要原子写入?

它似乎在有和没有原子写入的情况下都能工作,但当然没有原子写入更快。

我认为你的情况是安全的。我能看到的一个问题是,在 reader 读取双精度值之前,写入器线程只完成了一半的写入,从而导致一些损坏的值。例如(假设您使用的是 64 位整数),如果作者在之前有零的地方写了 -1 的值,但在 reader 读取它之前只写了前 4 个字节,那么 reader 会读到 40 亿,这是很遥远的事情,因为 -1 是二进制组件表示中的全部。

实际上,对于大多数现代架构而言,我认为这是不可能的。如果你有 64 位 cpu,读写内存应该发生在 64 位块中。这意味着上述腐败是不可能的。现在,如果您正在阅读将大型结构写入内存,那么您可能需要研究同步。

对于正确的可移植程序,您应该使用原子写入和读取。由 the standard 指定:

2.13.6 atomic Construct

[...] To avoid race conditions, all accesses of the locations designated by x that could potentially occur in parallel must be protected with an atomic construct.

更详细:

1.4.1 Structure of the OpenMP Memory Model

[...]

A single access to a variable may be implemented with multiple load or store instructions, and hence is not guaranteed to be atomic with respect to other accesses to the same variable.

[...]

if at least one thread reads from a memory unit and at least one thread writes without synchronization to that same memory unit, including cases due to atomicity considerations as described above, then a data race occurs. If a data race occurs then the result of the program is unspecified.

除了原子性,还要考虑可见性:

1.4.3 The Flush Operation

The memory model has relaxed-consistency because a thread’s temporary view of memory is not required to be consistent with memory at all times. A value written to a variable can remain in the thread’s temporary view until it is forced to memory at a later time. Likewise, a read from a variable may retrieve the value from the thread’s temporary view, unless it is forced to read from memory. The OpenMP flush operation enforces consistency between the temporary view and memory.

这意味着,除非您有任何显式或隐式内存刷新,否则无法保证您将永远看到更新后的值。

然而,原子版本并不一定慢。编译器是实现原子操作的人,它知道体系结构的特定内存模型并可以自由利用它。事实上,gcc 和 clang generate expensive locks 都不能用于在 x86 上原子地写入或读取 double,而这样做是为了原子增量或 long double 操作。不幸的是,原子可能仍然会阻碍某些优化——但如果您省略 atomic,这些很可能会导致未指定的结果。不要低估编译器优化:使用表面上合理但严格来说不符合标准的程序很容易出现未定义的行为。

至于内存刷新对性能的影响,你要看你的实际算法,你需要刷新内存的频率。