OpenMP atomic中write与update子句的区别及write子句读写操作的结果疑问
嗨,我来帮你捋清楚这个问题~
首先先明确OpenMP里atomic的write和update子句到底是干啥的:
write子句:它只负责保证写入变量的操作是原子性的——也就是说,当多个线程同时往同一个变量写值时,不会出现写操作被打断、变量值被写乱的情况,但它完全不关心你写入的新值是怎么来的,也不保护读取变量的过程。所以write适合的场景是:你要给变量赋值一个不依赖于它当前值的新值,比如sum = 100;这种,用write能确保这个写入不会和其他线程的写入冲突。update子句:它是把「读取变量当前值 → 根据当前值计算新值 → 写入新值」这一整套操作打包成一个不可分割的原子操作。像sum = sum + 1这种既有读又有写、还依赖变量当前值的操作,才是update的主场——它能彻底避免多个线程同时读写导致的数据竞争。
接下来解释你遇到的“奇怪”情况:你用write保护sum = sum + 1,按道理读取sum的过程没有被保护,应该会出现线程A和线程B同时读到同一个sum值,然后都加1写回去,导致最终结果偏小的情况,但你每次都得到了正确结果。这大概率是以下几个原因导致的“偶然正确”:
硬件的原子读取特性:对于
int这种对齐的基础数据类型,大多数现代处理器的读取操作本身就是原子性的(一次总线周期就能完成,不会被打断)。但即使读是原子的,读和写之间的时间差还是可能引发竞争——只是你当前的测试没撞上而已,不是必然正确。编译器的“偷偷优化”:有些编译器可能会识别出
sum = sum +1这种典型的读改写操作,即使你写了write子句,它也可能偷偷生成update语义的代码(比如x86架构下加lock前缀的原子指令),让你的代码意外达到了正确效果。测试场景的概率问题:虽然理论上存在竞争,但1亿次循环分给64线程,可能竞争发生的概率没高到每次测试都能触发,刚好你跑的几次都没碰到冲突的情况。
但必须强调:你的代码写法是不符合OpenMP规范的正确用法的,write子句不应该用来保护依赖当前变量值的赋值操作。这种“偶然正确”是不可靠的,换个编译器、换个硬件环境,或者测试次数足够多,大概率会出现结果不正确的情况。
正确的写法应该用update子句(或者直接省略子句,因为默认的atomic操作对这类表达式就是update语义):
#include <stdio.h> #include <omp.h> int main() { int sum = 0; #pragma omp parallel for num_threads(64) for (int i = 1; i <= 100000000; i++) { #pragma omp atomic update // 或者直接写 #pragma omp atomic sum = sum + 1; } printf("Sum with update: %d\n", sum); return 0; }
这样才能确保整个读改写操作是原子性的,无论什么环境下结果都是正确的。
内容来源于stack exchange




