Do I always need to protect a variable by mutex / atomic?

huangapple go评论87阅读模式
英文:

Do I always need to protect a variable by mutex / atomic?

问题

假设我有很多线程和一个简单的、可平凡复制的非数组变量(如floatuint16_t等)称为flag只有一个线程经常设置变量的值,而其他线程只是从中读取值,不对其进行写入。在这种情况下,我是否需要将变量声明为原子变量或使用互斥锁进行保护?我知道当多个线程对变量进行写入时,必须进行保护,但在我的情况下是否有必要?这是否与平台相关?

英文:

Suppose I have lots of threads and a plain, trivially-copyable, non-array (basic types like float, uint16_t etc.) variable called flag. One and only one of the threads often sets the variable's value, while others only read value from it and do not write to it. Do I have to make a variable atomic or guard it by mutex in this case? I know I must protect a variable when multiple threads write to it, but is it necessary to do in my case? Is it platfrom-dependent?

答案1

得分: 7

编译器可以对此进行优化:

while (x != 0) {
  // 编译器知道这段代码不会修改 x,也不会与其他线程同步
}

优化为:

if (x != 0) {
  while (true) {
    // 编译器知道这段代码不会修改 x
  }
}

也就是说,只检查一次 x,并在逻辑上假设它不会被改变。

然而,如果 x 是原子类型,编译器就不能这样做,因为每次读取都会隐式地同步其他线程对该变量的写入。

一般来说,在线程 1 中从一个变量读取而在线程 2 中没有同步写入相同变量的情况下,根据当前的 C++ 内存模型,这是未定义行为。

这种未定义行为既可能是硬件问题,也可能是编译器以你可能感到敌对的方式优化代码的许可。

因此,是的,你需要告诉编译器你的读取/写入可能涉及跨线程通信。

有趣的是,你的代码可能在今天、在你的硬件上、在当前的编译器上工作。然而,几年后,一个无害的操作系统更新、编译器更新或硬件修订将导致你的代码失败。更重要的是,失败的情况可能很少见,甚至可能在今天就发生!

你无法证明你的代码是正确的,因为我可以证明你的代码是错误的。你也无法证明编译器(假设它没有错误)将永远生成符合你要求的有效汇编代码,因为标准规定你的代码存在未定义行为。

你可能能够获取编译器的某次运行的汇编输出,并证明在你的 CPU 制造商为这些指令提供的保证下,生成的代码是正确的。但是我见过 CPU 指令描述,祝你好运。

英文:

Compilers are free to optimize this:

while (x != 0) {
  // code it knows does not modify x, nor synchronize with other threads
}

into

if (x!=0) {
  while (true) {
    // code it knows does not modify x
  }
}

ie, check x once and logically assume it cannot be changed.

If x is atomic, however, they are not allowed to do this, because each read implicitly synchronizes happens-after other threads writes to the variable.

In general, reading from a variable in thread 1 without synchronization of writing to the same variable in thread 2 is UB under the current C++ memory model.

This UB can both be a hardware issue, and permission for the compiler to optimize your code in ways you might feel are hostile.

So yes, you need to tell the compiler that your reads/writes are possibly going to involve inter-thread communication.

The fun part is that your code might work today, on your hardware with your current compiler. Then a few years from now, an innocuous operating system update, compiler update or hardware revision will make your code fail. What more, the failure case might be rare, and might even happen today!

You can't prove your code to be correct, because I can prove your code incorrect. You can't prove that the compiler (assuming it doesn't have bugs) will forevermore generate valid assembly that does what you ask, because the standard says your code exhibits UB.

You might be able to take the assembler output of a particular run of your compiler and prove that under the guarantees that your CPU manufacturer gives for those instructions that the produced code is correct. But I've seen CPU instruction descriptions, and good luck with that.

huangapple
  • 本文由 发表于 2023年8月9日 04:42:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/76863090.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定