在 C/C++ 中,易失性变量是否保证在线程之间具有最终一致的语义?

发布于 2024-09-07 03:08:33 字数 431 浏览 4 评论 0原文

是否有任何普遍遵循的标准(ISO C 或 C++,或 任何 POSIX/SUS 规范)表示变量(也许 标记为易失性),不受互斥体保护,正在访问 如果是的话,多个线程将变得最终一致 分配给?

举一个具体的例子,考虑两个线程共享一个 变量 v,初始值为零。

主题 1: v = 1

线程 2: 而(v==0) 屈服();

线程 2 是否保证最终终止?或者可以吗 可以想象永远旋转,因为缓存一致性永远不会消失 并使分配在线程 2 的缓存中可见?

我知道 C 和 C++ 标准(在 C++0x 之前)不涉及 所有关于线程或并发。但我很好奇 C++0x 是否 内存模型、pthreads 或其他任何东西都可以保证这一点。 (显然,这实际上可以在 32 位 x86 上的 Windows 上运行;我想知道它是否是一般可以依赖的东西,或者它是否只是碰巧在那里工作)。

Is there any guarantee by any commonly followed standard (ISO C or C++, or
any of the POSIX/SUS specifications) that a variable (perhaps
marked volatile), not guarded by a mutex, that is being accessed
by multiple threads will become eventually consistent if it is
assigned to?

To provide a specific example, consider two threads sharing a
variable v, with initial value zero.

Thread 1:
v = 1

Thread 2:
while(v == 0)
yield();

Is thread 2 guaranteed to terminate eventually? Or can it
conceivably spin forever because the cache coherency never kicks
in and makes the assignment visible in thread 2's cache?

I'm aware the C and C++ standards (before C++0x) do not speak at
all about threads or concurrency. But I'm curious if the C++0x
memory model, or pthreads, or anything else, guarantees this.
(Apparently this does actually work on Windows on 32-bit x86; I'm wondering if it's something that can be relied on generally or if it just happens to work there).

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

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

发布评论

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

评论(6

長街聽風 2024-09-14 03:08:33

这将取决于您的架构。虽然需要显式缓存刷新或内存同步来确保内存写入对其他线程可见的情况并不常见,但没有什么可以阻止它,而且我确实遇到过需要显式指令的平台(包括我当前正在开发的基于 PowerPC 的设备)必须执行以确保状态被刷新。

请注意,像互斥体这样的线程同步原语将根据需要执行必要的工作,但如果您想要的只是确保状态可见而不关心一致性,那么您通常实际上不需要线程同步原语 - 只需同步/刷新指令即可够了。

编辑:对于仍然对 volatile 关键字感到困惑的人 - volatile 保证编译器不会生成在寄存器中显式缓存数据的代码,但这不是此或,或这篇 Dobbs 博士的文章,或 这个所以问题,或者只是选择你最喜欢的针对弱一致性内存架构(如 Cell)的编译器,编写一些测试代码并将编译器生成的内容与您需要的内容进行比较,以确保写入对其他进程可见。

It's going to depend on your architecture. While it is unusual to require an explicit cache flush or memory sync to ensure memory writes are visible to other threads, nothing precludes it, and I've certainly encountered platforms (including the PowerPC-based device I am currently developing for) where explicit instructions have to be executed to ensure state is flushed.

Note that thread synchronisation primitives like mutexes will perform the necessary work as required, but you don't typically actually need a thread synchronisation primitive if all you want is to ensure the state is visible without caring about consistency - just the sync / flush instruction will suffice.

EDIT: To anyone still in confustion about the volatile keyword - volatile guarantees the compiler will not generate code that explicitly caches data in registers, but this is NOT the same thing as dealing with hardware that transparently caches / reorders reads and writes. Read e.g. this or this, or this Dr Dobbs article, or the answer to this SO question, or just pick your favourite compiler that targets a weakly consistent memory architecture like Cell, write some test code and compare what the compiler generates to what you'd need in order to ensure writes are visible to other processes.

情绪少女 2024-09-14 03:08:33

如果我正确理解了相关部分,C++0X 不会保证它适用于独立变量,甚至是易失性变量(易失性不是为这种用途而设计的),但会引入您可以保证的原子类型(参见标题)。

If I've understood correctly the relevant sections, C++0X won't guaranteed it for standalone variable or even volatile one (volatile isn't designed for that use), but will introduce atomic types for which you'll have the guarantee (see header <atomic>).

从此见与不见 2024-09-14 03:08:33

首先,如果它没有被标记为易失性,那么编译器很可能只会加载它一次。因此,无论内存最终是否发生变化,都不能保证编译会设置它。

由于您明确地说“无互斥体”,因此 pthreads 不适用。

除此之外,由于 C++ 没有内存模型,因此它取决于硬件架构。

First off, if it's not marked volatile there is a good chance the compiler may only load it once. So regardless of whether the memory eventually changes, there is no guarantee the compile will set it.

Since you explicitly say "no mutexes", pthreads doesn't apply.

Beyond that, since C++ does not have a memory model, it depends on the hardware architecture.

萌化 2024-09-14 03:08:33

这是一场潜在的数据竞赛。

对于 POSIX 线程,这就是 UB。我相信与 C++ 相同。

实际上,我无法想象它怎么会失败。

This is a potential data race.

With respect to POSIX thread, this is UB. Same with C++ I believe.

In practice I cannot imagine how it could fail.

熟人话多 2024-09-14 03:08:33

线程 2 是否保证最终终止?或者它可以永远旋转,因为缓存一致性永远不会启动并使分配在线程 2 的缓存中可见?

如果变量不是易失性的,则无法保证。在 C++0x 之前,标准对线程没有任何规定,并且由于变量不是易失性的,读/写不被认为是可观察到的副作用,因此允许编译器作弊。在 C++0x 之后,它是一个竞争条件,被明确声明为未定义的行为。

如果变量是易失性的,则可以保证读/写将会发生,并且编译器不会针对其他易失性内存访问进行重新排序。 (但是,这本身并不能保证 CPU 不会对这些内存访问进行重新排序——只是编译器不会)

但是您不能保证它不会相对于其他非易失性访问进行重新排序,所以你可能不会得到你期望的行为。特别是,如果编译器认为这样做是安全的(并且有益的),那么您试图“保护”的 while 循环之后的一些指令可能会被移到循环之前。但在执行此分析时,它只查看当前线程,而不查看其他线程中发生的情况。

所以,一般来说,即使使用易失性,也不能保证它能正常工作。可能会,而且可能经常会,但并非总是如此(这取决于循环后发生的情况)。这取决于编译器愿意进行多大程度的优化。但允许足以破坏代码。所以不要依赖它。如果您想围绕类似的事情进行同步,请使用内存屏障。这就是他们的目的。 (如果你这样做,你甚至不再需要易失性了)

Is thread 2 guaranteed to terminate eventually? Or can it conceivably spin forever because the cache coherency never kicks in and makes the assignment visible in thread 2's cache?

If the variable is not volatile, you have no guarantees. Pre-C++0x, the standard just has nothing to say about threads, and since the variable is not volatile, reads/writes are not considered observable side effects, so the compiler is allowed to cheat. Post-C++0x, it's a race condition, which is explicitly stated to be undefined behavior.

If the variable is volatile, you get the guarantee that reads/writes will happen, and that the compiler won't be reordered with respect to other volatile memory accesses. (However, this does not by itself guarantee that the CPU won't reorder these memory accesses -- just that the compiler won't)

But you have no guarantee that it won't be reordered with respect to other non-volatile accesses, so you might not get the behavior you expected. In particular, some of the instructions after the while loop, which you're trying to "protect" may be moved up ahead of the loop if the compiler deems it safe (and beneficial) to do so. But in performing this analysis, it only looks at the current thread, not what happens in other threads.

So no, in general, it is not guaranteed to work correctly, even with volatile. It might, and it probably often will, but not always (and it depends on what happens after the loop). It depends on how far the compiler is willing to go with optimizations. But it is allowed to go far enough to break the code. So don't rely on it. If you want to synchronize around something like this, use memory barriers. That's what they're for. (And if you do that, you don't even need the volatile any more)

是伱的 2024-09-14 03:08:33

我认为它最终会在任何平台上运行,但不知道您可能会看到延迟。

但老实说,对事件进行轮询等待的方式确实很糟糕。即使您做了yield,您的流程也会一次又一次地重新安排,而无需执行任何操作。

既然您已经知道如何将变量放置在两者都可以访问的地方,为什么不使用正确的工具来进行不消耗资源的等待呢?一对 pthread_mutex_tpthread_cond_t 应该可以完美地实现这一目的。

I think it will work eventually on any platform, but no idea on the delay that you may see.

But honestly, it is really bad style to do a polling wait for an event. Even though you do a yield your process will be rescheduled again and again, without doing anything.

Since you already know how to place a variable somewhere where it is accessible to both, why not use the right tools to do a wait that doesn't eat up resources? A pair of pthread_mutex_t and pthread_cond_t should perfectly do the trick.

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