一个线程读取,另一个线程写入volatile变量——是否线程安全?

4
在C语言中,我有一个指针,它被声明为volatile并初始化为null。
void* volatile pvoid;

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

我相信我可以不使用互斥量或条件变量而得以解决。
有没有什么原因导致线程1读取损坏的值或者线程2写入损坏的值呢?

6个回答

4
为了使其线程安全,您必须对变量进行原子读写操作,它作为volatile在所有时序情况下都不安全。在Win32下有Interlocked函数,在Linux下,如果您不想使用重量级互斥锁和条件变量,可以使用汇编构建
如果您不反对GPL,则http://www.threadingbuildingblocks.org及其atomic<>模板看起来很有前途。该库是跨平台的。

1
我有点困惑。一个变量如何被声明为volatile就能保证它是线程安全的呢? - Laz
涉及到volatile的内存屏障对于原子机制的工作至关重要,但仅是整个机制的一部分。 - jdehaan

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

好的,所以在某些平台上,指针地址可能无法正常工作。但是你的答案表明,它可能适用于检查布尔标志是否从0变为非零,因为即使读取值已损坏,它仍可能返回非零值,并且只有在写入发生后才会这样做。如果损坏的值恰好为零,则检查线程只需在下一次读取时获取它。是这样吗? - Michael Chinen
数据损坏可能会发生,因为读取中断了写入,其中一个或另一个需要超过单个指令。如果您的标志是单个字节,则我无法想到可能被中断的情况,但不能保证标志和指针是串行写入的。如果您有原子操作,则它们比标志更好。 - drawnonward

1

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

当然,安全的假设是使用互斥锁保护指针的访问。但是根据您描述的问题,我想知道pthread_once是否是更好的选择。毫无疑问,问题中没有足够的信息来确定哪种方法更好。


0

很遗憾,在纯C中你不能做出任何原子操作的假设。

然而,GCC提供了一些原子内置函数,可以为你处理许多架构的适当指令。有关更多信息,请参见GCC手册第5.47章


0

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

使用互斥锁..简单明了..这样您就可以确保没有同步错误!


你总是会遇到这种情况,即使你将其加载到寄存器中或不加载,它也可能会发生改变。 - progrmr

-2

在大多数平台上,指针值可以在单个指令中读取/写入,它要么被设置了,要么还没有被设置。它不能在中间被打断并包含损坏的值。在这种类型的平台上不需要互斥锁。


3
在所有平台上都不是真的。 - jdehaan
这种方案在哪些平台上无法运行?16位整数的CPU? - progrmr
存在着具有非32位内存访问的系统,特别是在DSP领域。此外,在32位内存访问必须对齐且被访问的值未对齐的情况下也会出现这种情况。 - Andrew Aylett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接