一个线程读取和另一个写入 volatile 变量 - 线程安全?

Posted

技术标签:

【中文标题】一个线程读取和另一个写入 volatile 变量 - 线程安全?【英文标题】:One thread reading and another writing to volatile variable - thread-safe? 【发布时间】:2010-06-19 18:03:34 【问题描述】:

在 C 中,我有一个声明为 volatile 并初始化为 null 的指针。

void* volatile pvoid;

线程 1 偶尔会读取指针值以检查它是否为非空。线程 1 不会设置指针的值。 线程 2 将只设置一次指针的值。

我相信我可以在不使用互斥锁或条件变量的情况下摆脱困境。 线程 1 会读取损坏的值或线程 2 会写入损坏的值是否有任何原因?

【问题讨论】:

【参考方案1】:

为了使其线程安全,您必须对变量进行原子读/写,它的易失性在所有时间情况下都不是安全的。 Win32下有Interlocked函数,Linux下如果你不想使用重量级的互斥锁和条件变量,可以build it yourself with assembly。

如果您不反对 GPL,那么 http://www.threadingbuildingblocks.org 及其 atomic<> 模板似乎很有希望。该库是跨平台的。

【讨论】:

我有点困惑。可变的变量如何首先确保它是线程安全的? 与 volatile 相关的内存屏障对于原子机制的工作至关重要,但只是整体的一部分。【参考方案2】:

在值适合单个寄存器的情况下,例如内存对齐指针,这是安全的。在其他可能需要多条指令来读取或写入值的情况下,读取线程可能会获取损坏的数据。如果您不确定读写是否在所有使用场景中都采用一条指令,请使用原子读写。

【讨论】:

好的,所以在某些平台上它可能无法很好地用于指针地址。但是您的回答表明,检查布尔标志是否从 0 变为非零可能是可以的,因为即使读取值已损坏,它仍可能返回非零,并且只有在写入发生之后。如果损坏的值恰好为零,那么检查线程只会在下一次读取时获取它。是吗? 会发生损坏,因为读取中断了写入,并且其中一个或另一个需要多条指令。如果您的标志是单个字节,那么我想不出它可能会被中断的情况,但是不能保证标志和指针是串行写入的。如果你有原子操作,它们是比标志更好的选择。【参考方案3】:

取决于您的编译器、架构和操作系统。 POSIX(因为这个问题被标记为 pthreads 我假设我们不是在谈论 Windows 或其他线程模型)和 C 没有给出足够的约束来为这个问题提供一个可移植的答案。

安全的假设当然是用互斥锁来保护对指针的访问。但是,根据您对问题的描述,我想知道 pthread_once 是否不是更好的方法。当然,问题中没有足够的信息来说明一种或另一种方式。

【讨论】:

【参考方案4】:

不幸的是,您无法对纯 C 中的原子性做出任何假设。

但是,GCC 确实提供了一些原子内置函数,可以为您使用适用于许多体系结构的正确指令。请参阅Chapter 5.47 of the GCC manual 了解更多信息。

【讨论】:

【参考方案5】:

嗯,这看起来不错.. 唯一的问题会发生在这种情况下 让线程 A 是您的检查线程,而 B 是修改线程.. 问题是检查相等性在技术上不是原子的,首先应该将值复制到寄存器然后检查然后恢复。让我们假设线程 A 已复制到寄存器,现在 B 决定更改 value ,现在变量的值发生了变化。因此,当控制权返回到 A 时,它会说它不为空,即使它 SHUD 是根据线程被调用的时间。这在这个程序中似乎无害,但可能会导致问题..

使用互斥锁..简单的 enuf.. 你可以确定你没有同步错误!

【讨论】:

你总是有这样的情况,你看后它会改变,不管你是否将它加载到寄存器中。【参考方案6】:

在可以在单个指令中读取/写入指针值的大多数平台上,它要么已设置,要么尚未设置。它不能在中间被中断并包含损坏的值。在那种平台上不需要互斥锁。

【讨论】:

并非所有平台都如此 这不适用于哪些平台?具有 16 位整数的 CPU? 具有非 32 位内存访问的系统确实存在,尤其是在 DSP 领域。此外,32 位内存访问必须对齐且访问的值未对齐。

以上是关于一个线程读取和另一个写入 volatile 变量 - 线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

Volatile关键字

Java volatile关键字介绍

volatile关键字作用

volatile关键字

Volatile关键字

13Java并发性和多线程-Java Volatile关键字