C++具有由函数访问的全局共享变量的 volatile 关键字

发布于 2024-09-17 16:47:23 字数 510 浏览 1 评论 0原文

我有一个多线程 C++ 应用程序。

现在我知道,对于全局共享变量,在某些情况下应该在检查变量的状态时使用 volatile,否则编译器可能会假设变量的值永远不会改变(在该线程中)。

但是,如果我调用返回变量值的方法而不是检查变量的状态,该怎么办?例如:

static int num = 0;

...

void foo()
{
   while(getNum() == 0)
   {
      // do something (or nothing)
   }
}

我还需要将 num 设为 volatile 变量吗?或者编译器是否认识到,由于我正在使用一种方法来访问该变量 num,因此它不会缓存结果?

有人有什么想法吗?

提前致谢,

〜Julian

编辑:在我的 while 循环中,我删除了 sleep 调用,并将其替换为通用的内容,例如执行某事(或不执行任何操作)的注释

I have a multi-threaded C++ application.

Now I know that for global shared variables, you should use volatile in some cases while checking the state of the variable or else the compiler could assume that the variable's value never changes (in that thread).

What if, however, instead of checking the status of a variable I call a method that returns the value of the variable? For instance:

static int num = 0;

...

void foo()
{
   while(getNum() == 0)
   {
      // do something (or nothing)
   }
}

Would I still have to make num a volatile variable? or does the compiler recognize that since I'm using a method to access that variable num it won't cache the result?

Anyone got any ideas?

Thanks in advance,

~Julian

edit: inside my while loop I removed the sleep call and replaced it with something generic such as a comment to do something (or nothing)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

叹沉浮 2024-09-24 16:47:23

不,只要您进行必要的同步,就永远不需要易失性

调用线程库同步函数,无论它们在您的平台上是什么,都应该注意使本地“缓存”值无效并使编译器重新加载全局变量。

在这种特殊情况下,sleep 很可能会产生这样的效果,但无论如何它都不是一个好的实现。 num 上应该有一个条件变量,用 setter 函数保护它,并让 setter 函数向 foo 发送信号。

至于具体问题,函数是否隐藏优化的访问是极其依赖于实现和情况的。最好的选择是在编译器的单独调用中编译 getter 函数,但即使如此,也无法保证不会发生过程间优化。例如,某些平台可能会将 IR 代码放入 .o 文件中,并在“链接器”阶段执行代码生成。

免责声明。

上面的关键词:1.只要你做了必要的同步和2.可能有这样的效果

1:睡眠或空的忙循环不是“必要的同步”。这不是编写多线程程序的正确方法。因此,在这种情况下可能需要 volatility。

2:是的,sleep 可能不会被 I/O 函数的实现计算在内,甚至可能被标记为纯粹且无副作用。在这种情况下,全局的易失性是必要的。然而,我怀疑是否真的有任何分布式实现会破坏这样的睡眠循环,因为不幸的是它们很常见。

No, volatile is never needed as long as you're doing the necessary synchronization.

Calling thread library synchronization functions, whatever they are on your platform, should take care of invalidating locally "cached" values and making the compiler reloads globals.

In this particular case, sleep is likely to have such an effect, but it's not a good implementation anyway. There should be a condition variable on num, protect it with a setter function, and have the setter function send a signal to foo.

As to the specific question, whether the function hides the access from optimization is extremely implementation- and situation-dependent. Your best bet is to compile the getter function in a separate invokation of the compiler, but even then, there's no way to guarantee that interprocedural optimization doesn't occur. For example, some platforms may put IR code in the .o files and perform code generation in the "linker" stage.

Disclaimer.

Key words above: 1. as long as you're doing the necessary synchronization and 2. likely to have such an effect.

1: sleep or an empty busy-loop are not "necessary synchronization." That is not the correct way to write a multithreaded program, period. So, volatile may be needed in such cases.

2: Yes, sleep may not be counted by the implementation an I/O function, and may even be labeled as pure and free of side-effects. In that case, volatile on the global would be necessary. However, I doubt any implementations have really been distributed which would break sleep loops like that, since they are unfortunately common.

故人爱我别走 2024-09-24 16:47:23

您提出的基本上是对“易失性”的滥用,其真正目的是告诉编译器该变量可能会被外部硬件或系统中的其他进程修改,因此,每次使用时都需要从内存中读取它。

它不会保护您免受程序中的线程冲突等影响,尽管在您的情况下,您似乎正在使用该标志来发出关闭请求信号。

如果您知道只有一个控制线程会更新变量,那么在不进行同步等的情况下执行此操作实际上是可以的。另外,我会使用位操作来设置标志,因为这在更多硬件上更有可能是“原子的”。

num && x'00000001' 

What you propose is basically a misuse of "volatile", its real purpose is to tell the compiler that the variable may be modified by external hardware or another process in the system, therefore, it needs to be really read from memory everytime its used.

It will not protect you from thread collisions etc. within your program, although in your case it looks like you are using the flag to signal a shutdown request.

Its actually OK to do this without synchronising etc. provided you know that only a single controlling thread will update the variable. Also I would use a bit manipulation to set the flag as this is more likely to be "atomic" on more hardware.

num && x'00000001' 
黯然#的苍凉 2024-09-24 16:47:23

不幸的是,易失性语义有点软弱。易失性的概念并不是真正用于线程。

Potatoswatter 是正确的,调用操作系统同步原语通常会阻止优化编译器从循环中提升 num 的读取。但它的工作原理与使用访问器方法的工作原理相同......偶然。

编译器看到您调用的函数不能立即用于内联或分析,因此它必须假设可以由其他函数使用的任何变量都可以在该不透明函数中读取或更改。因此,在进行调用之前,编译器需要将所有这些“全局”变量写回内存。

在 corensic,我们向 jinx.h 添加了一个内联函数,以更直接的方式执行此操作。如下所示:

 inline void memory_barrier() { asm volatile("nop" ::: "memory"); }

这是相当微妙的,但它有效地告诉编译器(gcc)它无法摆脱这块不透明asm,并且不透明asm可以读取或写入任何全局可见的内存块。这有效地阻止了编译器跨此边界重新排序加载/存储。

对于您的示例:

memory_barrier();
而(数字== 0){
内存_屏障();
...
现在

num 的读取被卡在原地。更重要的是,它与其他代码的关系固定不变。所以你可以:

 while (flag == 0) { memory_barrier(); }  // spin
 process data[0..N]

而另一个线程则可以:

 populate data[0..N]
 memory_barrier();
 flag = 1;

PS。如果您执行此类操作(本质上是创建您自己的同步原语),性能优势可能很大,但质量风险很高。 Jinx 特别擅长发现这些无锁结构中的错误。因此,您可能想使用它或其他一些工具来帮助测试这些东西。

聚苯硫醚。 Linux 社区有一篇关于此问题的很好的帖子,名为“挥发性被认为是有害的”,请查看。

Unfortunately volatile semantics are kinda wishy-washy. The concept of volatile wasn't really meant to be used for threading.

Potatoswatter is correct that calling the OS synchronization primitives will normally prevent the optimizing compiler from hoisting the read of num from the loop. But it works for sorta the same reason that using an accessor method works... by accident.

The compiler sees you calling a function that isn't immediately available for inlining or analysis, so it has to assume that any variable that could be used by some other function might be read or altered in that opaque function. So before doing the call, the compiler needs to write all those "global" variables back to memory.

At corensic, we added an inline function to jinx.h that does this in a more direct way. Something like the following:

 inline void memory_barrier() { asm volatile("nop" ::: "memory"); }

This is rather subtle, but it effectively tells the compiler (gcc) that it can't get rid of this chunk of opaque asm and that the opaque asm can read or write any globally visible piece of memory. This effectively stops the compiler from reordering loads/stores across this boundary.

For your example:

memory_barrier();
while (num == 0) {
memory_barrier();
...
}

Now the read of num is stuck in place. And potentially more importantly, it's stuck in place with relation to other code. So you could have:

 while (flag == 0) { memory_barrier(); }  // spin
 process data[0..N]

And another thread does:

 populate data[0..N]
 memory_barrier();
 flag = 1;

PS. If you do this type of thing (essentially creating your own synchronization primitives) the perf wins can be big but the quality risk is high. Jinx is particularly good at finding bugs in these lock-free structures. So you might want to use it or some other tool to help test this stuff.

PPS. The linux community has a nice post about this called "volatile considered harmful", check it out.

淡忘如思 2024-09-24 16:47:23

从技术上讲,它确实需要被标记为易失性的。只要代码继续符合 C++ 抽象机规范,编译器就可以自由地做他们想做的事情来优化代码。具有足够资源的符合标准的编译器可以在程序的整个生命周期内内联 getNum 的所有实例,将 num 的值移动到寄存器中(或者简单地注意到它从未被任何代码实际更改,将其视为常量) 。

实际上,(当前)CPU 没有足够的可用寄存器,即使是最积极的优化编译器也会选择这样做。但如果需要正确性,则需要 volatility。

technically it does need to be marked volatile. Compilers are free to do what they want in order to optimise code as long as it continues to conform to the c++ abstract machine spec. A conforming compiler, with enough resources, could inline all instances of getNum, move the value of num into a register (or simply, noticing that its never actually changed by any code, treat it as a constant) for the entire lifetime of the program.

Practically speaking, no (current) CPU has enough free registers that even the most agressive optimizing compiler would choose to do that. But if correctness is desired - volatile is required.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文