windows - Windows 中的原子性、易变性和线程安全

标签 windows multithreading thread-safety atomic volatility

我对原子性的理解是,它用于确保一个值将被整体而不是部分地读/写。例如,一个 64 位值实际上是两个 32 位 DWORD(此处假设 x86)在线程之间共享时必须是原子的,以便同时读/写两个 DWORD。这样一来,一个线程就无法读取未更新的一半变量。如何保证原子性?

此外,我的理解是波动性根本不能保证线程安全。是真的吗?

我已经看到它在很多地方暗示简单的原子/ volatile 是线程安全的。我不明白那是怎么回事。我是否也不需要内存屏障来确保任何值(无论是原子值还是其他值)在实际保证在其他线程中被读/写之前被读/写?

例如,假设我创建了一个挂起的线程,进行一些计算以将一些值更改为线程可用的结构,然后恢复,例如:

HANDLE hThread = CreateThread(NULL, 0, thread_entry, (void *)&data, CREATE_SUSPENDED, NULL);
data->val64 = SomeCalculation();
ResumeThread(hThread);

我想这取决于 ResumeThread 中的任何内存屏障?我应该为 val64 进行联锁交换吗?如果线程正在运行,情况会怎样?

我确定我在这里问了很多,但基本上我想弄清楚的是我在标题中提出的问题:对 Windows 中的原子性、易变性和线程安全性的一个很好的解释。谢谢

最佳答案

it's used to make sure a value will be read/written in whole

这只是原子性的一小部分。它的核心意思是“不可中断”,即处理器上的一条指令,其副作用不能与另一条指令交错。按照设计,当内存更新可以用单个内存总线周期执行时,它是原子的。这需要内存位置的地址对齐,以便单个周期可以更新它。未对齐的访问需要额外的工作,一部分字节由一个周期写入,一部分由另一个周期写入。现在它不再是不间断的了。

获取对齐更新非常容易,这是编译器提供的保证。或者,更广泛地说,通过编译器实现的内存模型。它只是选择对齐的内存地址,有时故意留下几个字节的未使用间隙以使下一个变量对齐。对大于处理器 native 字长的变量的更新永远不可能是原子的。

但更重要的是使线程工作所需的处理器指令类型。每个处理器都实现了 CAS instruction 的变体,比较和交换。它是实现同步所需的核心原子指令。更高级别的同步原语,如监视器(也称为条件变量)、互斥量、信号、关键部分和信号量,都构建在该核心指令之上。

这是最低限度,处理器通常会提供额外的处理器来使简单的操作原子化。就像增加一个变量一样,它的核心是一个可中断的操作,因为它需要一个读-修改-写操作。需要它是原子的是很常见的,大多数 C++ 程序都依赖它来实现引用计数。

volatility does not guarantee thread safety at all

事实并非如此。这是一个可以追溯到更轻松的时代的属性,那时候机器只有一个处理器内核。它只影响代码生成,特别是代码优化器试图消除内存访问并使用处理器寄存器中值的副本的方式。从寄存器中读取值比从内存中读取值快 3 倍,这对代码执行速度有很大的影响。

应用volatile 确保代码优化器不会认为寄存器中的值是准确的并强制它再次读取内存。它真正只对自身不稳定的内存值类型、通过内存映射 I/O 公开其寄存器的设备很重要。它一直被严重滥用,因为它的核心含义是试图将语义置于内存模型较弱的处理器之上,Itanium 是最令人震惊的例子。您今天使用 volatile 得到的结果在很大程度上取决于您使用的特定编译器和运行时。切勿将其用于线程安全,而应始终使用同步原语。

simply being atomic/volatile is thread-safe

如果这是真的,编程会简单得多。原子操作只涵盖非常简单的操作,真正的程序往往需要保持整个对象线程安全。以原子方式更新其所有成员,并且从不公开部分更新的对象的 View 。像迭代列表这样简单的事情就是一个核心示例,您不能在查看列表元素时让另一个线程修改列表。这就是您需要使用更高级别的同步原语的时候,这种原语可以在安全继续之前阻止代码。

真实的程序通常会遇到这种同步需求并表现出 Amdahls' law行为。换句话说,添加一个额外的线程实际上并不能使程序更快。有时实际上使它变慢。谁能为此找到更好的捕鼠器,谁就能保证获得诺贝尔奖,我们还在等待。

关于windows - Windows 中的原子性、易变性和线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28123375/

相关文章:

c++ - 在屏幕上定位程序位置

.net - AutoResetEvent 类型是原子开关的合适选择吗?

multithreading - 我可以使用 pthreads 分配每个线程索引吗?

c - 为什么我的线程安全代码不起作用?

ruby-on-rails - (免费)Windows 上带有文件夹 View 的文本编辑器?

c++ - 生成windows电脑的UID

c# - Windows 8 备份/恢复应用程序

java - Java中线程的空闲时间是什么意思?

Python 使用 Threading 生成一个线程,并在 main 完成时终止

Java - 使用Thread而不是SwingWorker