简体   繁体   English

C++ volatile 关键字是否引入了内存栅栏?

[英]Does the C++ volatile keyword introduce a memory fence?

I understand that volatile informs the compiler that the value may be changed, but in order to accomplish this functionality, does the compiler need to introduce a memory fence to make it work?我知道volatile通知编译器该值可能会更改,但是为了完成此功能,编译器是否需要引入内存栅栏才能使其工作?

From my understanding, the sequence of operations on volatile objects cannot be reordered and must be preserved.根据我的理解,对 volatile 对象的操作顺序不能重新排序,必须保留。 This seems to imply some memory fences are necessary and that there isn't really a way around this.这似乎意味着一些内存栅栏是必要的,并且没有真正的解决方法。 Am I correct in saying this?我这样说对吗?


There is an interesting discussion at this related question这个相关问题上有一个有趣的讨论

Jonathan Wakely writes :乔纳森·韦克利写道

... Accesses to distinct volatile variables cannot be reordered by the compiler as long as they occur in separate full expressions ... right that volatile is useless for thread-safety, but not for the reasons he gives. ... 对不同 volatile 变量的访问不能被编译器重新排序,只要它们出现在单独的完整表达式中 ... 没错, volatile 对于线程安全是无用的,但不是因为他给出的原因。 It's not because the compiler might reorder accesses to volatile objects, but because the CPU might reorder them.这不是因为编译器可能会重新排序对易失性对象的访问,而是因为 CPU 可能会重新排序它们。 Atomic operations and memory barriers prevent the compiler and the CPU from reordering原子操作和内存屏障阻止编译器和 CPU 重新排序

To which David Schwartz replies in the comments : David Schwartz 在评论中回复:

... There's no difference, from the point of view of the C++ standard, between the compiler doing something and the compiler emitting instructions that cause the hardware to do something. ... 从 C++ 标准的角度来看,编译器做某事和编译器发出导致硬件做某事的指令之间没有区别。 If the CPU may reorder accesses to volatiles, then the standard doesn't require that their order be preserved.如果 CPU 可以重新排序对 volatile 的访问,则标准不要求保留它们的顺序。 ... ...

... The C++ standard doesn't make any distinction about what does the reordering. ... C++ 标准对重新排序的内容没有任何区别。 And you can't argue that the CPU can reorder them with no observable effect so that's okay -- the C++ standard defines their order as observable.并且您不能争辩说 CPU 可以在没有可观察效果的情况下对它们重新排序,所以没关系——C++ 标准将它们的顺序定义为可观察的。 A compiler is compliant with the C++ standard on a platform if it generates code that makes the platform do what the standard requires.如果编译器生成的代码使平台执行标准要求,则编译器在平台上符合 C++ 标准。 If the standard requires accesses to volatiles not be reordered, then a platform the reorders them isn't compliant.如果标准要求不重新排序对 volatile 的访问,则重新排序它们的平台不兼容。 ... ...

My point is that if the C++ standard prohibits the compiler from reordering accesses to distinct volatiles, on the theory that the order of such accesses is part of the program's observable behavior, then it also requires the compiler to emit code that prohibits the CPU from doing so.我的观点是,如果 C++ 标准禁止编译器重新排序对不同 volatile 的访问,理论上这种访问的顺序是程序可观察行为的一部分,那么它还要求编译器发出禁止 CPU 执行的代码所以。 The standard does not differentiate between what the compiler does and what the compiler's generate code makes the CPU do.该标准没有区分编译器做什么和编译器生成代码让 CPU 做什么。

Which does yield two questions: Is either of them "right"?这确实会产生两个问题:它们中的任何一个“正确”吗? What do actual implementations really do?实际的实现到底做了什么?

Rather than explaining what volatile does, allow me to explain when you should use volatile .与其解释volatile作用,不如让我解释一下您何时应该使用volatile

  • When inside a signal handler.在信号处理程序内部时。 Because writing to a volatile variable is pretty much the only thing the standard allows you to do from within a signal handler.因为写入volatile变量几乎是标准允许您在信号处理程序中执行的唯一操作。 Since C++11 you can use std::atomic for that purpose, but only if the atomic is lock-free.从 C++11 开始,您可以为此目的使用std::atomic ,但前提是原子是无锁的。
  • When dealing with setjmp according to Intel . 根据 Intel处理setjmp
  • When dealing directly with hardware and you want to ensure that the compiler does not optimize your reads or writes away.当直接处理硬件并且您希望确保编译器不会优化您的读取或写入时。

For example:例如:

volatile int *foo = some_memory_mapped_device;
while (*foo)
    ; // wait until *foo turns false

Without the volatile specifier, the compiler is allowed to completely optimize the loop away.如果没有volatile说明符,则允许编译器完全优化循环。 The volatile specifier tells the compiler that it may not assume that 2 subsequent reads return the same value. volatile说明符告诉编译器它可能不会假设 2 个后续读取返回相同的值。

Note that volatile has nothing to do with threads.请注意, volatile与线程无关。 The above example does not work if there was a different thread writing to *foo because there is no acquire operation involved.如果有一个不同的线程写入*foo ,上面的例子就不起作用,因为不涉及获取操作。

In all other cases, usage of volatile should be considered non-portable and not pass code review anymore except when dealing with pre-C++11 compilers and compiler extensions (such as msvc's /volatile:ms switch, which is enabled by default under X86/I64).在所有其他情况下, volatile使用应该被认为是不可移植的,并且不再通过代码审查,除非处理 C++11 之前的编译器和编译器扩展(例如 msvc 的/volatile:ms开关,在X86/I64)。

Does the C++ volatile keyword introduce a memory fence? C++ volatile 关键字是否引入了内存栅栏?

A C++ compiler which conforms to the specification is not required to introduce a memory fence.符合规范的 C++ 编译器不需要引入内存栅栏。 Your particular compiler might;您的特定编译器可能会; direct your question to the authors of your compiler.将您的问题直接交给编译器的作者。

The function of "volatile" in C++ has nothing to do with threading. C++中“volatile”的功能与线程无关。 Remember, the purpose of "volatile" is to disable compiler optimizations so that reading from a register that is changing due to exogenous conditions is not optimized away.请记住,“易失性”的目的是禁用编译器优化,以便读取由于外生条件而发生变化的寄存器不会被优化掉。 Is a memory address that is being written to by a different thread on a different CPU a register that is changing due to exogenous conditions?由不同 CPU 上的不同线程写入的内存地址是否是由于外生条件而发生变化的寄存器? No. Again, if some compiler authors have chosen to treat memory addresses being written to by different threads on different CPUs as though they were registers changing due to exogenous conditions, that's their business;不。同样,如果某些编译器作者选择将不同 CPU 上的不同线程写入的内存地址视为由于外生条件而更改的寄存器,那是他们的事; they are not required to do so.他们不需要这样做。 Nor are they required -- even if it does introduce a memory fence -- to, for instance, ensure that every thread sees a consistent ordering of volatile reads and writes.也不需要它们——即使它确实引入了内存栅栏——例如,确保每个线程看到易失性读取和写入的一致顺序。

In fact, volatile is pretty much useless for threading in C/C++.事实上, volatile 对于 C/C++ 中的线程几乎没有用处。 Best practice is to avoid it.最好的做法是避免它。

Moreover: memory fences are an implementation detail of particular processor architectures.此外:内存栅栏是特定处理器架构的实现细节。 In C#, where volatile explicitly is designed for multithreading, the specification does not say that half fences will be introduced, because the program might be running on an architecture that doesn't have fences in the first place.在C#中,其中挥发性明确专为多线程,该规范并没有说半挡片将被引入,因为该计划可能会在不具有在首位围栏的架构上运行。 Rather, again, the specification makes certain (extremely weak) guarantees about what optimizations will be eschewed by the compiler, runtime and CPU to put certain (extremely weak) constraints on how some side effects will be ordered.相反,该规范再次确定(极弱)保证编译器、运行时和 CPU 将避免哪些优化,以对某些副作用的排序方式施加某些(极弱)约束。 In practice these optimizations are eliminated by use of half fences, but that's an implementation detail subject to change in the future.在实践中,通过使用半围栏消除了这些优化,但这是一个实现细节,将来可能会发生变化。

The fact that you care about the semantics of volatile in any language as they pertain to multithreading indicates that you're thinking about sharing memory across threads.您关心任何语言中 volatile 的语义,因为它们与多线程有关,这一事实表明您正在考虑跨线程共享内存。 Consider simply not doing that.考虑不这样做。 It makes your program far harder to understand and far more likely to contain subtle, impossible-to-reproduce bugs.它使您的程序更难理解,并且更有可能包含微妙的、无法重现的错误。

What David is overlooking is the fact that the C++ standard specifies the behavior of several threads interacting only in specific situations and everything else results in undefined behavior. David 忽略了一个事实,即 C++ 标准指定了仅在特定情况下交互的多个线程的行为,而其他一切都会导致未定义的行为。 A race condition involving at least one write is undefined if you don't use atomic variables.如果不使用原子变量,则涉及至少一次写入的竞争条件是未定义的。

Consequently, the compiler is perfectly in its right to forego any synchronization instructions since your CPU will only notice the difference in a program that exhibits undefined behavior due to missing synchronization.因此,编译器完全有权放弃任何同步指令,因为您的 CPU 只会注意到由于缺少同步而表现出未定义行为的程序中的差异。

First of all, the C++ standards do not guarantee the memory barriers needed for properly ordering the read / writes that are non atomic.首先,C++ 标准不保证正确排序非原子读/写所需的内存屏障。 volatile variables are recommended for using with MMIO, signal handling, etc. On most implementations volatile is not useful for multi-threading and it's not generally recommended.建议将volatile变量与 MMIO、信号处理等一起使用。在大多数实现中, volatile对多线程没有用,并且通常不推荐使用。

Regarding the implementation of volatile accesses, this is the compiler choice.关于 volatile 访问的实现,这是编译器的选择。

This article , describing gcc behavior shows that you cannot use a volatile object as a memory barrier to order a sequence of writes to volatile memory. 这篇描述gcc行为的文章表明,您不能使用易失性对象作为内存屏障来对一系列写入易失性内存进行排序。

Regarding icc behavior I found this source telling also that volatile does not guarantee ordering memory accesses.关于icc行为,我发现这个来源还告诉我 volatile 不保证对内存访问进行排序。

Microsoft VS2013 compiler has a different behavior. Microsoft VS2013编译器有不同的行为。 This documentation explains how volatile enforces Release / Acquire semantics and enables volatile objects to be used in locks / releases on multi-threaded applications.文档解释了 volatile 如何强制执行 Release / Acquire 语义,并使 volatile 对象能够在多线程应用程序的锁定/释放中使用。

Another aspect that needs to be taken into considerations is that the same compiler may have a different behavior wrt.需要考虑的另一个方面是相同的编译器可能具有不同的行为。 to volatile depending on the targeted hardware architecture .易失性取决于目标硬件架构 This post regarding the MSVS 2013 compiler clearly states the specifics of compiling with volatile for ARM platforms.这篇关于 MSVS 2013 编译器的 帖子清楚地说明了在 ARM 平台上使用 volatile 进行编译的细节。

So my answer to:所以我的回答是:

Does the C++ volatile keyword introduce a memory fence? C++ volatile 关键字是否引入了内存栅栏?

would be: Not guaranteed, probably not but some compilers might do it.将是:不保证,可能不会,但有些编译器可能会这样做。 You should not rely on the fact that it does.你不应该依赖它确实如此。

The compiler only inserts a memory fence on the Itanium architecture, as far as I know.据我所知,编译器只在安腾架构上插入内存栅栏。

The volatile keyword is really best used for asynchronous changes, eg, signal handlers and memory-mapped registers; volatile关键字最适合用于异步更改,例如,信号处理程序和内存映射寄存器; it is usually the wrong tool to use for multithreaded programming.它通常是用于多线程编程的错误工具。

It depends on which compiler "the compiler" is.这取决于“编译器”是哪个编译器。 Visual C++ does, since 2005. But the Standard does not require it, so some other compilers do not.自 2005 年以来,Visual C++ 确实如此。但标准不需要它,因此其他一些编译器不需要。

It doesn't have to.它没有必要。 Volatile is not a synchronization primitive. Volatile 不是同步原语。 It just disables optimisations, ie you get a predictable sequence of reads and writes within a thread in the same order as prescribed by the abstract machine.它只是禁用优化,即您在一个线程内按照抽象机器规定的相同顺序获得可预测的读取和写入序列。 But reads and writes in different threads have no order in the first place, it makes no sense to speak of preserving or not preserving their order.但是在不同线程中的读取和写入首先是没有顺序的,谈论保留或不保留它们的顺序是没有意义的。 The order between theads can be established by synchronization primitives, you get UB without them. theads 之间的顺序可以通过同步原语建立,没有它们你会得到 UB。

A bit of explanation regarding memory barriers.关于内存屏障的一些解释。 A typical CPU has several levels of memory access.典型的 CPU 具有多个级别的内存访问。 There is a memory pipeline, several levels of cache, then RAM etc.有一个内存管道,几个级别的缓存,然后是 RAM 等。

Membar instructions flush the pipeline. Membar 指令冲洗管道。 They don't change the order in which reads and writes are executed, it just forces outstanding ones to be executed at a given moment.它们不会改变读取和写入的执行顺序,它只是强制在给定时刻执行未完成的。 It is useful for multithreaded programs, but not much otherwise.它对多线程程序很有用,但除此之外就没什么用了。

Cache(s) are normally automatically coherent between CPUs.缓存通常在 CPU 之间自动保持一致。 If one wants to make sure the cache is in sync with RAM, cache flush is needed.如果要确保缓存与 RAM 同步,则需要缓存刷新。 It is very different from a membar.它与 membar 非常不同。

This is largely from memory, and based on pre-C++11, without threads.这主要来自内存,并且基于 C++11 之前的版本,没有线程。 But having participated in discussions on threading in the committe, I can say that there was never an intent by the committee that volatile could be used for synchronization between threads.但是在参与了提交中关于线程的讨论后,我可以说委员会从来没有打算将volatile用于线程之间的同步。 Microsoft proposed it, but the proposal didn't carry.微软提出了它,但该提议没有通过。

The key specification of volatile is that access to a volatile represents an "observable behavior", just like IO. volatile的关键规范是对volatile的访问代表一种“可观察的行为”,就像 IO 一样。 In the same way the compiler cannot reorder or remove specific IO, it cannot reorder or remove accesses to a volatile object (or more correctly, accesses through an lvalue expression with volatile qualified type).以同样的方式,编译器不能重新排序或删除特定的 IO,它不能重新排序或删除对 volatile 对象的访问(或更准确地说,通过具有 volatile 限定类型的左值表达式访问)。 The original intent of volatile was, in fact, to support memory mapped IO.事实上,volatile 的初衷是支持内存映射 IO。 The "problem" with this, however, is that it is implementation defined what constitutes a "volatile access".然而,与此有关的“问题”在于实现定义了什么构成了“易失性访问”。 And many compilers implement it as if the definition was "an instruction which reads or writes to memory has been executed".许多编译器将其实现为好像定义是“已执行读取或写入内存的指令”。 Which is a legal, albeit useless definition, if the implementation specifies it.这是一个合法的,尽管无用的定义,如果实现指定它。 (I've yet to find the actual specification for any compiler.) (我还没有找到任何编译器的实际规范。)

Arguably (and it's an argument I accept), this violates the intent of the standard, since unless the hardware recognizes the addresses as memory mapped IO, and inhibits any reordering, etc., you can't even use volatile for memory mapped IO, at least on Sparc or Intel architectures.可以说(这是我接受的一个论点),这违反了标准的意图,因为除非硬件将地址识别为内存映射 IO,并禁止任何重新排序等,否则您甚至不能将 volatile 用于内存映射 IO,至少在 Sparc 或 Intel 架构上。 Never the less, none of the comilers I've looked at (Sun CC, g++ and MSC) do output any fence or membar instructions.尽管如此,我看过的编译器(Sun CC、g++ 和 MSC)都没有输出任何栅栏或 membar 指令。 (About the time Microsoft proposed extending the rules for volatile , I think some of their compilers implemented their proposal, and did emit fence instructions for volatile accesses. I've not verified what recent compilers do, but it wouldn't surprise me if it depended on some compiler option. The version I checkd—I think it was VS6.0—didn't emit fences, however.) (关于微软提议扩展volatile规则的时候,我认为他们的一些编译器实现了他们的提议,并且确实为 volatile 访问发出了栅栏指令。我没有验证最近的编译器做了什么,但如果它我也不会感到惊讶取决于一些编译器选项。然而,我检查的版本 - 我认为是 VS6.0 - 没有发出围栏。)

The compiler needs to introduce a memory fence around volatile accesses if, and only if, that is necessary to make the uses for volatile specified in the standard work ( setjmp , signal handlers, and so on) on that particular platform.编译器需要引入周围的存储栅栏volatile的访问,当且仅当,这是必要的,以便为使用volatile标准工作规定( setjmp那个特定的平台上,信号处理,等等)。

Note that some compilers do go way beyond what's required by the C++ standard in order to make volatile more powerful or useful on those platforms.请注意,某些编译器确实超出了 C++ 标准的要求,以便在这些平台上使volatile更加强大或有用。 Portable code shouldn't rely on volatile to do anything beyond what's specified in the C++ standard.可移植代码不应该依赖volatile来做任何超出 C++ 标准规定的事情。

I always use volatile in interrupt service routines, eg the ISR (often assembly code) modifies some memory location and the higher level code that runs outside of the interrupt context accesses the memory location through a pointer to volatile.我总是在中断服务例程中使用 volatile,例如 ISR(通常是汇编代码)修改一些内存位置,而在中断上下文之外运行的更高级别代码通过指向 volatile 的指针访问内存位置。

I do this for RAM as well as memory-mapped IO.我为 RAM 以及内存映射 IO 执行此操作。

Based on the discussion here it seems this is still a valid use of volatile but doesn't have anything to do with multiple threads or CPUs.根据这里的讨论,这似乎仍然是 volatile 的有效用法,但与多线程或 CPU 没有任何关系。 If the compiler for a microcontroller "knows" that there can't be any other accesses (eg everyting is on-chip, no cache and there's only one core) I would think that a memory fence isn't implied at all, the compiler just needs to prevent certain optimisations.如果微控制器的编译器“知道”不能有任何其他访问(例如,所有内容都在片上,没有缓存并且只有一个内核),我会认为根本不暗示内存栅栏,编译器只需要防止某些优化。

As we pile more stuff into the "system" that executes the object code almost all bets are off, at least that's how I read this discussion.随着我们在执行目标代码的“系统”中加入更多的东西,几乎所有的赌注都被取消了,至少我是这么读这个讨论的。 How could a compiler ever cover all bases?编译器怎么可能涵盖所有基础?

I think the confusion around volatile and instruction reordering stems from the 2 notions of reorderings CPUs do:我认为关于 volatile 和指令重新排序的混淆源于 CPU 重新排序的 2 个概念:

  1. Out-of-order execution.乱序执行。
  2. Sequence of memory read/writes as seen by other CPUs (reordering in a sense that each CPU might see a different sequence).其他 CPU 看到的内存读/写顺序(重新排序,每个 CPU 可能会看到不同的顺序)。

Volatile affects how a compiler generates the code assuming single threaded execution (this includes interrupts). Volatile 会影响编译器如何生成假定单线程执行的代码(这包括中断)。 It doesn't imply anything about memory barrier instructions, but it rather precludes a compiler from performing certain kinds of optimizations related to memory accesses.它并不暗示任何关于内存屏障指令的内容,而是阻止编译器执行与内存访问相关的某些类型的优化。
A typical example is re-fetching a value from memory, instead of using one cached in a register.一个典型的例子是从内存中重新获取一个值,而不是使用缓存在寄存器中的值。

Out-of-order execution乱序执行

CPUs can execute instructions out-of-order/speculatively provided that the end result could have happened in the original code.如果最终结果可能发生在原始代码中,CPU 可以乱序/推测性地执行指令。 CPUs can perform transformations that are disallowed in compilers because compilers can only perform transformations which are correct in all circumstances. CPU 可以执行编译器不允许的转换,因为编译器只能执行在所有情况下都正确的转换。 In contrast, CPUs can check the validity of these optimizations and back out of them if they turn out to be incorrect.相比之下,CPU 可以检查这些优化的有效性,如果结果不正确,则退出它们。

Sequence of memory read/writes as seen by other CPUs其他 CPU 所见的内存读/写顺序

The end result of a sequence of instruction, the effective order, must agree with the semantics of the code generated by a compiler.指令序列的最终结果,即有效顺序,必须与编译器生成的代码的语义一致。 However the actual execution order chosen by the CPU can be different.然而,CPU 选择的实际执行顺序可能不同。 The effective order as seen in other CPUs (every CPU can have a different view) can be constrained by memory barriers.在其他 CPU 中看到的有效顺序(每个 CPU 可以有不同的视图)可能会受到内存屏障的限制。
I'm not sure how much effective and actual order can differ because I don't know to what extent memory barriers can preclude CPUs from performing out-of-order execution.我不确定有效顺序和实际顺序有多少不同,因为我不知道内存屏障在多大程度上可以阻止 CPU 执行乱序执行。

Sources:资料来源:

While I was working through an online downloadable video tutorial for 3D Graphics & Game Engine development working with modern OpenGL.当我在学习使用现代 OpenGL 进行 3D 图形和游戏引擎开发的在线可下载视频教程时。 We did use volatile within one of our classes.我们确实在我们的一个类中使用了volatile The tutorial website can be found here and the video working with the volatile keyword is found in the Shader Engine series video 98. These works are not of my own but are accredited to Marek A. Krzeminski, MASc and this is an excerpt from the video download page.教程网站可以在这里找到,使用volatile关键字的视频可以在Shader Engine系列视频 98 中找到。这些作品不是我自己的,而是由Marek A. Krzeminski, MASc ,这是视频的摘录下载页面。

"Since we can now have our games run in multiple threads it is important to synchronize data between threads properly. In this video I show how to create a volitile locking class to ensure volitile variables are properly synchronized..." “由于我们现在可以让我们的游戏在多个线程中运行,因此在线程之间正确同步数据很重要。在本视频中,我展示了如何创建一个 volitile 锁定类以确保 volitile 变量正确同步......”

And if you are subscribed to his website and have access to his video's within this video he references this article concerning the use of Volatile with multithreading programming.如果您订阅了他的网站并可以在此视频中访问他的视频,他会参考这篇关于在multithreading编程中使用Volatile 文章

Here is the article from the link above: http://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766这是上面链接中的文章: http : //www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766

volatile: The Multithreaded Programmer's Best Friend volatile:多线程程序员最好的朋友

By Andrei Alexandrescu, February 01, 2001作者:Andrei Alexandrescu,2001 年 2 月 1 日

The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events.设计 volatile 关键字是为了防止编译器优化可能在出现某些异步事件时使代码不正确。

I don't want to spoil your mood, but this column addresses the dreaded topic of multithreaded programming.我不想破坏您的心情,但本专栏讨论了多线程编程这个可怕的话题。 If — as the previous installment of Generic says — exception-safe programming is hard, it's child's play compared to multithreaded programming.如果——正如 Generic 的前一期所说——异常安全编程很难,与多线程编程相比,它是小菜一碟。

Programs using multiple threads are notoriously hard to write, prove correct, debug, maintain, and tame in general.众所周知,使用多线程的程序通常难以编写、证明正确、调试、维护和驯服。 Incorrect multithreaded programs might run for years without a glitch, only to unexpectedly run amok because some critical timing condition has been met.不正确的多线程程序可能会运行多年而不会出现故障,但由于满足了某些关键的时序条件,才会意外地运行异常。

Needless to say, a programmer writing multithreaded code needs all the help she can get.不用说,编写多线程代码的程序员需要她所能获得的所有帮助。 This column focuses on race conditions — a common source of trouble in multithreaded programs — and provides you with insights and tools on how to avoid them and, amazingly enough, have the compiler work hard at helping you with that.本专栏重点关注竞争条件——多线程程序中常见的问题根源——并为您提供有关如何避免它们的见解和工具,而且令人惊讶的是,编译器会努力帮助您解决这个问题。

Just a Little Keyword只是一个小关键词

Although both C and C++ Standards are conspicuously silent when it comes to threads, they do make a little concession to multithreading, in the form of the volatile keyword.尽管 C 和 C++ 标准在涉及线程时都明显保持沉默,但它们确实以 volatile 关键字的形式对多线程做出了一些让步。

Just like its better-known counterpart const, volatile is a type modifier.就像它最著名的对应物 const 一样, volatile 是一个类型修饰符。 It's intended to be used in conjunction with variables that are accessed and modified in different threads.它旨在与在不同线程中访问和修改的变量结合使用。 Basically, without volatile, either writing multithreaded programs becomes impossible, or the compiler wastes vast optimization opportunities.基本上,没有 volatile,要么编写多线程程序变得不可能,要么编译器浪费大量优化机会。 An explanation is in order.一个解释是有条理的。

Consider the following code:考虑以下代码:

 class Gadget { public: void Wait() { while (!flag_) { Sleep(1000); // sleeps for 1000 milliseconds } } void Wakeup() { flag_ = true; } ... private: bool flag_; };

The purpose of Gadget::Wait above is to check the flag_ member variable every second and return when that variable has been set to true by another thread.上面 Gadget::Wait 的目的是每秒检查 flag_ 成员变量,并在该变量被另一个线程设置为 true 时返回。 At least that's what its programmer intended, but, alas, Wait is incorrect.至少这是它的程序员的意图,但是,唉,Wait 是不正确的。

Suppose the compiler figures out that Sleep(1000) is a call into an external library that cannot possibly modify the member variable flag_.假设编译器发现 Sleep(1000) 是对无法修改成员变量 flag_ 的外部库的调用。 Then the compiler concludes that it can cache flag_ in a register and use that register instead of accessing the slower on-board memory.然后编译器得出结论,它可以将 flag_ 缓存在寄存器中并使用该寄存器而不是访问速度较慢的板载内存。 This is an excellent optimization for single-threaded code, but in this case, it harms correctness: after you call Wait for some Gadget object, although another thread calls Wakeup, Wait will loop forever.这是对单线程代码的极好优化,但在这种情况下,它损害了正确性:在您为某个 Gadget 对象调用 Wait 后,尽管另一个线程调用了 Wakeup,但 Wait 将永远循环。 This is because the change of flag_ will not be reflected in the register that caches flag_.这是因为flag_的变化不会反映在缓存flag_的寄存器中。 The optimization is too ... optimistic.优化太……乐观了。

Caching variables in registers is a very valuable optimization that applies most of the time, so it would be a pity to waste it.在寄存器中缓存变量是一种非常有价值的优化,大部分时间都适用,因此浪费它会很可惜。 C and C++ give you the chance to explicitly disable such caching. C 和 C++ 为您提供了显式禁用此类缓存的机会。 If you use the volatile modifier on a variable, the compiler won't cache that variable in registers — each access will hit the actual memory location of that variable.如果在变量上使用 volatile 修饰符,编译器将不会将该变量缓存在寄存器中——每次访问都会命中该变量的实际内存位置。 So all you have to do to make Gadget's Wait/Wakeup combo work is to qualify flag_ appropriately:因此,要使 Gadget 的 Wait/Wakeup 组合起作用,您所要做的就是适当地限定 flag_:

 class Gadget { public: ... as above ... private: volatile bool flag_; };

Most explanations of the rationale and usage of volatile stop here and advise you to volatile-qualify the primitive types that you use in multiple threads.对 volatile 的基本原理和用法的大多数解释到此为止,并建议您对在多线程中使用的原始类型进行 volatile 限定。 However, there is much more you can do with volatile, because it is part of C++'s wonderful type system.但是,您可以使用 volatile 做更多事情,因为它是 C++ 美妙的类型系统的一部分。

Using volatile with User-Defined Types对用户定义的类型使用 volatile

You can volatile-qualify not only primitive types, but also user-defined types.您不仅可以对原始类型进行 volatile 限定,还可以对用户定义的类型进行 volatile 限定。 In that case, volatile modifies the type in a way similar to const.在这种情况下, volatile 以类似于 const 的方式修改类型。 (You can also apply const and volatile to the same type simultaneously.) (您也可以同时将 const 和 volatile 应用于同一类型。)

Unlike const, volatile discriminates between primitive types and user-defined types.与 const 不同,volatile 区分原始类型和用户定义的类型。 Namely, unlike classes, primitive types still support all of their operations (addition, multiplication, assignment, etc.) when volatile-qualified.也就是说,与类不同,原始类型在 volatile 限定时仍然支持它们的所有操作(加法、乘法、赋值等)。 For example, you can assign a non-volatile int to a volatile int, but you cannot assign a non-volatile object to a volatile object.例如,您可以将非 volatile int 分配给 volatile int,但不能将非 volatile 对象分配给 volatile 对象。

Let's illustrate how volatile works on user-defined types on an example.让我们通过一个例子来说明 volatile 如何处理用户定义的类型。

 class Gadget { public: void Foo() volatile; void Bar(); ... private: String name_; int state_; }; ... Gadget regularGadget; volatile Gadget volatileGadget;

If you think volatile is not that useful with objects, prepare for some surprise.如果您认为 volatile 对对象没有那么有用,请准备好迎接惊喜。

 volatileGadget.Foo(); // ok, volatile fun called for // volatile object regularGadget.Foo(); // ok, volatile fun called for // non-volatile object volatileGadget.Bar(); // error! Non-volatile function called for // volatile object!

The conversion from a non-qualified type to its volatile counterpart is trivial.从非限定类型到其 volatile 对应物的转换是微不足道的。 However, just as with const, you cannot make the trip back from volatile to non-qualified.但是,就像使用 const 一样,您无法从 volatile 返回到非限定。 You must use a cast:您必须使用演员表:

 Gadget& ref = const_cast<Gadget&>(volatileGadget); ref.Bar(); // ok

A volatile-qualified class gives access only to a subset of its interface, a subset that is under the control of the class implementer.一个 volatile 限定的类只允许访问它的接口的一个子集,这个子集在类实现者的控制之下。 Users can gain full access to that type's interface only by using a const_cast.用户只能通过使用 const_cast 获得对该类型接口的完全访问权限。 In addition, just like constness, volatileness propagates from the class to its members (for example, volatileGadget.name_ and volatileGadget.state_ are volatile variables).此外,就像常量一样,易失性从类传播到其成员(例如, volatileGadget.name_ 和 volatileGadget.state_ 是易失性变量)。

volatile, Critical Sections, and Race Conditions volatile、临界区和竞争条件

The simplest and the most often-used synchronization device in multithreaded programs is the mutex.多线程程序中最简单也是最常用的同步设备是互斥锁。 A mutex exposes the Acquire and Release primitives.互斥锁公开 Acquire 和 Release 原语。 Once you call Acquire in some thread, any other thread calling Acquire will block.一旦您在某个线程中调用 Acquire,任何其他调用 Acquire 的线程都会阻塞。 Later, when that thread calls Release, precisely one thread blocked in an Acquire call will be released.稍后,当该线程调用 Release 时,恰好在 Acquire 调用中阻塞的一个线程将被释放。 In other words, for a given mutex, only one thread can get processor time in between a call to Acquire and a call to Release.换句话说,对于给定的互斥锁,只有一个线程可以在调用 Acquire 和调用 Release 之间获得处理器时间。 The executing code between a call to Acquire and a call to Release is called a critical section.调用 Acquire 和调用 Release 之间的执行代码称为临界区。 (Windows terminology is a bit confusing because it calls the mutex itself a critical section, while "mutex" is actually an inter-process mutex. It would have been nice if they were called thread mutex and process mutex.) (Windows 术语有点令人困惑,因为它将互斥锁本身称为临界区,而“互斥锁”实际上是一个进程间互斥锁。如果将它们称为线程互斥锁和进程互斥锁就好了。)

Mutexes are used to protect data against race conditions.互斥体用于保护数据免受竞争条件的影响。 By definition, a race condition occurs when the effect of more threads on data depends on how threads are scheduled.根据定义,当更多线程对数据的影响取决于线程的调度方式时,就会发生竞争条件。 Race conditions appear when two or more threads compete for using the same data.当两个或多个线程竞争使用相同的数据时,就会出现竞争条件。 Because threads can interrupt each other at arbitrary moments in time, data can be corrupted or misinterpreted.由于线程可以在任意时刻相互中断,因此数据可能会被破坏或误解。 Consequently, changes and sometimes accesses to data must be carefully protected with critical sections.因此,必须使用临界区仔细保护更改和有时对数据的访问。 In object-oriented programming, this usually means that you store a mutex in a class as a member variable and use it whenever you access that class' state.在面向对象的编程中,这通常意味着您将互斥锁作为成员变量存储在类中,并在访问该类的状态时使用它。

Experienced multithreaded programmers might have yawned reading the two paragraphs above, but their purpose is to provide an intellectual workout, because now we will link with the volatile connection.有经验的多线程程序员在阅读上面两段时可能会打哈欠,但他们的目的是提供智力锻炼,因为现在我们将链接到 volatile 连接。 We do this by drawing a parallel between the C++ types' world and the threading semantics world.我们通过在 C++ 类型的世界和线程语义世界之间绘制一个平行线来做到这一点。

  • Outside a critical section, any thread might interrupt any other at any time;在临界区之外,任何线程都可能随时中断其他线程; there is no control, so consequently variables accessible from multiple threads are volatile.没有控制,因此可从多个线程访问的变量是可变的。 This is in keeping with the original intent of volatile — that of preventing the compiler from unwittingly caching values used by multiple threads at once.这符合 volatile 的初衷——防止编译器无意中缓存多个线程同时使用的值。
  • Inside a critical section defined by a mutex, only one thread has access.在互斥锁定义的临界区中,只有一个线程可以访问。 Consequently, inside a critical section, the executing code has single-threaded semantics.因此,在临界区中,执行代码具有单线程语义。 The controlled variable is not volatile anymore — you can remove the volatile qualifier.受控变量不再是 volatile - 您可以删除 volatile 限定符。

In short, data shared between threads is conceptually volatile outside a critical section, and non-volatile inside a critical section.简而言之,线程之间共享的数据在概念上在临界区外是易失性的,而在临界区内是非易失性的。

You enter a critical section by locking a mutex.您可以通过锁定互斥锁来进入临界区。 You remove the volatile qualifier from a type by applying a const_cast.您可以通过应用 const_cast 从类型中删除 volatile 限定符。 If we manage to put these two operations together, we create a connection between C++'s type system and an application's threading semantics.如果我们设法将这两个操作放在一起,我们就在 C++ 的类型系统和应用程序的线程语义之间建立了联系。 We can make the compiler check race conditions for us.我们可以让编译器为我们检查竞争条件。

LockingPtr锁定指针

We need a tool that collects a mutex acquisition and a const_cast.我们需要一个收集互斥量获取和 const_cast 的工具。 Let's develop a LockingPtr class template that you initialize with a volatile object obj and a mutex mtx.让我们开发一个 LockingPtr 类模板,您可以使用易失性对象 obj 和互斥对象 mtx 对其进行初始化。 During its lifetime, a LockingPtr keeps mtx acquired.在其生命周期内,一个 LockingPtr 保持获取 mtx。 Also, LockingPtr offers access to the volatile-stripped obj.此外,LockingPtr 提供对 volatile-stripped obj 的访问。 The access is offered in a smart pointer fashion, through operator-> and operator*.通过operator-> 和operator* 以智能指针方式提供访问。 The const_cast is performed inside LockingPtr. const_cast 在 LockingPtr 内执行。 The cast is semantically valid because LockingPtr keeps the mutex acquired for its lifetime.强制转换在语义上是有效的,因为 LockingPtr 会在其生命周期内保留获取的互斥锁。

First, let's define the skeleton of a class Mutex with which LockingPtr will work:首先,让我们定义一个类 Mutex 的骨架,LockingPtr 将与它一起工作:

 class Mutex { public: void Acquire(); void Release(); ... };

To use LockingPtr, you implement Mutex using your operating system's native data structures and primitive functions.要使用 LockingPtr,您需要使用操作系统的本机数据结构和原始函数来实现互斥锁。

LockingPtr is templated with the type of the controlled variable. LockingPtr 使用受控变量的类型进行模板化。 For example, if you want to control a Widget, you use a LockingPtr that you initialize with a variable of type volatile Widget.例如,如果你想控制一个 Widget,你可以使用一个 LockingPtr,你用一个 volatile Widget 类型的变量初始化它。

LockingPtr's definition is very simple. LockingPtr 的定义非常简单。 LockingPtr implements an unsophisticated smart pointer. LockingPtr 实现了一个简单的智能指针。 It focuses solely on collecting a const_cast and a critical section.它只专注于收集 const_cast 和临界区。

 template <typename T> class LockingPtr { public: // Constructors/destructors LockingPtr(volatile T& obj, Mutex& mtx) : pObj_(const_cast<T*>(&obj)), pMtx_(&mtx) { mtx.Lock(); } ~LockingPtr() { pMtx_->Unlock(); } // Pointer behavior T& operator*() { return *pObj_; } T* operator->() { return pObj_; } private: T* pObj_; Mutex* pMtx_; LockingPtr(const LockingPtr&); LockingPtr& operator=(const LockingPtr&); };

In spite of its simplicity, LockingPtr is a very useful aid in writing correct multithreaded code.尽管它很简单,但 LockingPtr 是编写正确的多线程代码的非常有用的帮助。 You should define objects that are shared between threads as volatile and never use const_cast with them — always use LockingPtr automatic objects.您应该将线程之间共享的对象定义为 volatile 并且永远不要对它们使用 const_cast — 始终使用 LockingPtr 自动对象。 Let's illustrate this with an example.让我们用一个例子来说明这一点。

Say you have two threads that share a vector object:假设您有两个共享一个向量对象的线程:

 class SyncBuf { public: void Thread1(); void Thread2(); private: typedef vector<char> BufT; volatile BufT buffer_; Mutex mtx_; // controls access to buffer_ };

Inside a thread function, you simply use a LockingPtr to get controlled access to the buffer_ member variable:在线程函数中,您只需使用 LockingPtr 来控制对 buffer_ 成员变量的访问:

 void SyncBuf::Thread1() { LockingPtr<BufT> lpBuf(buffer_, mtx_); BufT::iterator i = lpBuf->begin(); for (; i != lpBuf->end(); ++i) { ... use *i ... } }

The code is very easy to write and understand — whenever you need to use buffer_, you must create a LockingPtr pointing to it.代码很容易编写和理解——每当你需要使用buffer_时,你必须创建一个指向它的LockingPtr。 Once you do that, you have access to vector's entire interface.一旦你这样做了,你就可以访问 vector 的整个界面。

The nice part is that if you make a mistake, the compiler will point it out:好的部分是,如果您犯了错误,编译器会指出:

 void SyncBuf::Thread2() { // Error! Cannot access 'begin' for a volatile object BufT::iterator i = buffer_.begin(); // Error! Cannot access 'end' for a volatile object for ( ; i != lpBuf->end(); ++i ) { ... use *i ... } }

You cannot access any function of buffer_ until you either apply a const_cast or use LockingPtr.在应用 const_cast 或使用 LockingPtr 之前,您无法访问 buffer_ 的任何函数。 The difference is that LockingPtr offers an ordered way of applying const_cast to volatile variables.不同之处在于 LockingPtr 提供了一种将 const_cast 应用于 volatile 变量的有序方式。

LockingPtr is remarkably expressive. LockingPtr 非常具有表现力。 If you only need to call one function, you can create an unnamed temporary LockingPtr object and use it directly:如果只需要调用一个函数,可以创建一个未命名的临时LockingPtr对象,直接使用:

 unsigned int SyncBuf::Size() { return LockingPtr<BufT>(buffer_, mtx_)->size(); }

Back to Primitive Types回到原始类型

We saw how nicely volatile protects objects against uncontrolled access and how LockingPtr provides a simple and effective way of writing thread-safe code.我们看到了 volatile 如何很好地保护对象免受不受控制的访问,以及 LockingPtr 如何提供一种简单有效的方法来编写线程安全代码。 Let's now return to primitive types, which are treated differently by volatile.现在让我们回到原始类型,它们被 volatile 区别对待。

Let's consider an example where multiple threads share a variable of type int.让我们考虑一个示例,其中多个线程共享一个 int 类型的变量。

 class Counter { public: ... void Increment() { ++ctr_; } void Decrement() { —ctr_; } private: int ctr_; };

If Increment and Decrement are to be called from different threads, the fragment above is buggy.如果要从不同的线程调用 Increment 和 Decrement,则上面的片段有问题。 First, ctr_ must be volatile.首先, ctr_ 必须是 volatile。 Second, even a seemingly atomic operation such as ++ctr_ is actually a three-stage operation.其次,即使是++ctr_这种看似原子的操作,其实也是一个三阶段操作。 Memory itself has no arithmetic capabilities.内存本身没有算术能力。 When incrementing a variable, the processor:当增加一个变量时,处理器:

  • Reads that variable in a register在寄存器中读取该变量
  • Increments the value in the register增加寄存器中的值
  • Writes the result back to memory将结果写回内存

This three-step operation is called RMW (Read-Modify-Write).这三步操作称为RMW(读-修改-写)。 During the Modify part of an RMW operation, most processors free the memory bus in order to give other processors access to the memory.在 RMW 操作的修改部分期间,大多数处理器释放内存总线,以便其他处理器访问内存。

If at that time another processor performs a RMW operation on the same variable, we have a race condition: the second write overwrites the effect of the first.如果此时另一个处理器对同一个变量执行 RMW 操作,我们就会遇到竞争条件:第二次写入会覆盖第一次写入的效果。

To avoid that, you can rely, again, on LockingPtr:为避免这种情况,您可以再次依赖 LockingPtr:

 class Counter { public: ... void Increment() { ++*LockingPtr<int>(ctr_, mtx_); } void Decrement() { —*LockingPtr<int>(ctr_, mtx_); } private: volatile int ctr_; Mutex mtx_; };

Now the code is correct, but its quality is inferior when compared to SyncBuf's code.现在代码是正确的,但与 SyncBuf 的代码相比,它的质量较差。 Why?为什么? Because with Counter, the compiler will not warn you if you mistakenly access ctr_ directly (without locking it).因为使用 Counter,如果您错误地直接访问 ctr_(未锁定它),编译器将不会警告您。 The compiler compiles ++ctr_ if ctr_ is volatile, although the generated code is simply incorrect.如果 ctr_ 是 volatile,编译器会编译 ++ctr_,尽管生成的代码完全不正确。 The compiler is not your ally anymore, and only your attention can help you avoid race conditions.编译器不再是你的盟友,只有你的注意力才能帮助你避免竞争条件。

What should you do then?那你应该怎么做? Simply encapsulate the primitive data that you use in higher-level structures and use volatile with those structures.简单地封装您在更高级别结构中使用的原始数据,并在这些结构中使用 volatile。 Paradoxically, it's worse to use volatile directly with built-ins, in spite of the fact that initially this was the usage intent of volatile!矛盾的是,直接将 volatile 与内置函数一起使用会更糟,尽管最初这是 volatile 的使用意图!

volatile Member Functions可变成员函数

So far, we've had classes that aggregate volatile data members;到目前为止,我们已经有了聚合易变数据成员的类; now let's think of designing classes that in turn will be part of larger objects and shared between threads.现在让我们考虑设计类,这些类反过来将成为更大对象的一部分并在线程之间共享。 Here is where volatile member functions can be of great help.这就是 volatile 成员函数可以提供很大帮助的地方。

When designing your class, you volatile-qualify only those member functions that are thread safe.在设计类时,您只对线程安全的成员函数进行 volatile 限定。 You must assume that code from the outside will call the volatile functions from any code at any time.您必须假设来自外部的代码将随时从任何代码调用 volatile 函数。 Don't forget: volatile equals free multithreaded code and no critical section;不要忘记:volatile 等于免费的多线程代码并且没有临界区; non-volatile equals single-threaded scenario or inside a critical section.非易失性等于单线程场景或在关键部分内。

For example, you define a class Widget that implements an operation in two variants — a thread-safe one and a fast, unprotected one.例如,您定义了一个 Widget 类,它在两个变体中实现了一个操作——一个线程安全的一个和一个快速的、不受保护的一个。

 class Widget { public: void Operation() volatile; void Operation(); ... private: Mutex mtx_; };

Notice the use of overloading.注意重载的使用。 Now Widget's user can invoke Operation using a uniform syntax either for volatile objects and get thread safety, or for regular objects and get speed.现在,Widget 的用户可以使用统一的语法为 volatile 对象调用 Operation 并获得线程安全性,或者为常规对象调用操作并获得速度。 The user must be careful about defining the shared Widget objects as volatile.用户在将共享 Widget 对象定义为 volatile 时必须小心。

When implementing a volatile member function, the first operation is usually to lock this with a LockingPtr.在实现 volatile 成员函数时,第一个操作通常是使用 LockingPtr 锁定它。 Then the work is done by using the non- volatile sibling:然后使用非易失性兄弟完成工作:

 void Widget::Operation() volatile { LockingPtr<Widget> lpThis(*this, mtx_); lpThis->Operation(); // invokes the non-volatile function }

Summary概括

When writing multithreaded programs, you can use volatile to your advantage.在编写多线程程序时,您可以使用 volatile。 You must stick to the following rules:您必须遵守以下规则:

  • Define all shared objects as volatile.将所有共享对象定义为 volatile。
  • Don't use volatile directly with primitive types.不要将 volatile 直接用于原始类型。
  • When defining shared classes, use volatile member functions to express thread safety.在定义共享类时,使用 volatile 成员函数来表达线程安全。

If you do this, and if you use the simple generic component LockingPtr, you can write thread-safe code and worry much less about race conditions, because the compiler will worry for you and will diligently point out the spots where you are wrong.如果你这样做,并且如果你使用简单的通用组件 LockingPtr,你可以编写线程安全的代码并且不用担心竞争条件,因为编译器会为你担心并且会努力指出你错的地方。

A couple of projects I've been involved with use volatile and LockingPtr to great effect.我参与的几个项目使用 volatile 和 LockingPtr 取得了很好的效果。 The code is clean and understandable.代码简洁易懂。 I recall a couple of deadlocks, but I prefer deadlocks to race conditions because they are so much easier to debug.我记得有几个死锁,但我更喜欢死锁而不是竞争条件,因为它们更容易调试。 There were virtually no problems related to race conditions.几乎没有与竞态条件相关的问题。 But then you never know.但是你永远不会知道。

Acknowledgements致谢

Many thanks to James Kanze and Sorin Jianu who helped with insightful ideas.非常感谢 James Kanze 和 Sorin Janu,他们提出了富有洞察力的想法。


Andrei Alexandrescu is a Development Manager at RealNetworks Inc. (www.realnetworks.com), based in Seattle, WA, and author of the acclaimed book Modern C++ Design. Andrei Alexandrescu 是位于华盛顿州西雅图的 RealNetworks Inc. (www.realnetworks.com) 的开发经理,并且着有广受好评的《现代 C++ 设计》一书。 He may be contacted at www.moderncppdesign.com.可以通过 www.moderncppdesign.com 与他联系。 Andrei is also one of the featured instructors of The C++ Seminar (www.gotw.ca/cpp_seminar). Andrei 还是 The C++ Seminar (www.gotw.ca/cpp_seminar) 的特邀讲师之一。

This article might be a little dated, but it does give good insight towards an excellent use of using the volatile modifier with in the use of multithreaded programming to help keep events asynchronous while having the compiler checking for race conditions for us.这篇文章可能有点过时,但它确实提供了很好的见解,可以很好地了解如何在多线程编程中使用 volatile 修饰符来帮助保持事件异步,同时让编译器为我们检查竞争条件。 This may not directly answer the OPs original question about creating a memory fence, but I choose to post this as an answer for others as an excellent reference towards a good use of volatile when working with multithreaded applications.这可能不会直接回答关于创建内存栅栏的 OP 原始问题,但我选择将其发布为其他人的答案,作为在使用多线程应用程序时良好使用 volatile 的极好参考。

The keyword volatile essentially means that reads and writes an object should be performed exactly as written by the program, and not optimized in any way .关键字volatile本质上意味着读取和写入对象应该完全按照程序写入的方式执行,而不是以任何方式进行优化 Binary code should follow C or C++ code: a load where this is read, a store where there is a write.二进制代码应该遵循 C 或 C++ 代码:读取的负载,写入的存储。

It also means that no read should be expected to result in a predictable value: the compiler shouldn't assume anything about a read even immediately following a write to the same volatile object:这也意味着不应预期读取会产生可预测的值:即使在写入相同的易失性对象之后,编译器也不应立即假设任何有关读取的内容:

volatile int i;
i = 1;
int j = i; 
if (j == 1) // not assumed to be true

volatile may be the most important tool in the "C is a high level assembly language" toolbox . volatile可能是“C 是高级汇编语言”工具箱中最重要的工具

Whether declaring an object volatile is sufficient for ensuring the behavior of code that deals with asynchronous changes depends on the platform: different CPU give different levels of guaranteed synchronization for normal memory reads and writes.声明一个对象 volatile 是否足以确保处理异步更改的代码的行为取决于平台:不同的 CPU 为正常的内存读取和写入提供不同级别的保证同步。 You probably shouldn't try to write such low level multithreading code unless you are an expert in the area.除非您是该领域的专家,否则您可能不应该尝试编写如此低级的多线程代码。

Atomic primitives provide a nice higher level view of objects for multithreading that makes it easy to reason about code.原子原语为多线程提供了一个很好的更高级别的对象视图,这使得代码推理变得容易。 Almost all programmers should use either atomic primitives or primitives that provide mutual exclusions like mutexes, read-write-locks, semaphores, or other blocking primitives.几乎所有程序员都应该使用原子原语或提供互斥的原语,如互斥锁、读写锁、信号量或其他阻塞原语。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM