Windows中的原子性、波动性和线程安全

Windows中的原子性、波动性和线程安全,windows,multithreading,thread-safety,atomicity,volatility,Windows,Multithreading,Thread Safety,Atomicity,Volatility,我对原子性的理解是,原子性用于确保一个值被整体而不是部分地读/写。例如,一个64位值实际上是两个32位DWORD(此处假设为x86),在线程之间共享时必须是原子的,以便两个DWORD同时读取/写入。这样一个线程就不能读取半个未更新的变量。你如何保证原子性 此外,据我所知,波动性根本不能保证线程安全。这是真的吗 我已经看到它暗示了许多地方,简单的原子/易失性是线程安全的。我不明白这是怎么回事。我是否也需要一个内存屏障来确保任何值(原子值或其他值)在能够保证在另一个线程中读/写之前都已读/写 例如,

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

此外,据我所知,波动性根本不能保证线程安全。这是真的吗

我已经看到它暗示了许多地方,简单的原子/易失性是线程安全的。我不明白这是怎么回事。我是否也需要一个内存屏障来确保任何值(原子值或其他值)在能够保证在另一个线程中读/写之前都已读/写

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

HANDLE hThread = CreateThread(NULL, 0, thread_entry, (void *)&data, CREATE_SUSPENDED, NULL);
data->val64 = SomeCalculation();
ResumeThread(hThread);
我想这取决于恢复线程中的任何内存障碍?我应该为val64进行联锁交换吗?如果线程正在运行,会如何改变情况


我肯定我在这里问了很多问题,但基本上我想弄明白的是我在标题中问的:一个关于Windows中原子性、波动性和线程安全性的好解释。感谢

,一般来说,C和C++不提供关于如何在多线程程序中读写“易失性”对象的任何保证。(“新的C++ 11可能是因为它现在包含了线程作为标准的一部分,但是传统线程不是标准C或C++的一部分。”使用易失性,并假设代码中的原子性和缓存一致性是一个问题。一个特定的编译器和平台是否会以线程安全的方式处理对“易失性”对象的访问,这是一个废话

一般规则是:“volatile”不足以确保线程安全访问。您应该使用一些平台提供的机制(通常是一些函数或同步对象)来安全地访问线程共享值

现在,特别是在Windows上,特别是在VC++2005+编译器上,特别是在x86和x64系统上,访问基本对象(如int)可以实现线程安全,前提是:

  • 在64位和32位窗口上,对象必须是32位类型,并且必须是32位对齐的
  • 在64位窗口上,对象也可以是64位类型,并且必须是64位对齐的
  • 它必须被声明为不稳定的 如果这些都是真的,那么对对象的访问将是不稳定的、原子的,并且被确保缓存一致性的指令包围。必须满足大小和对齐条件,以便编译器在访问对象时生成执行原子操作的代码。将对象声明为volatile可确保编译器不会进行与缓存以前可能读入寄存器的值相关的代码优化,并确保生成的代码在被访问时包含适当的内存屏障指令

    即便如此,您可能还是最好使用一些东西,比如用于访问小东西的Interlocated*函数,以及用于较大对象和数据结构的bog标准同步对象,比如互斥体或CriticalSection。理想情况下,获取库并使用已经包含适当锁的数据结构。让您的库和操作系统尽可能多地完成这项艰巨的工作

    在您的示例中,我希望无论线程是否启动,您都需要使用线程安全访问来更新val64

    如果线程已经在运行,那么您肯定需要某种类型的线程安全写入val64,可以使用Interchange64或类似工具,也可以通过获取和释放某种类型的同步对象来执行适当的内存屏障指令。同样,线程也需要使用线程安全访问器来读取它

    在线程还没有恢复的情况下,就不那么清晰了。ResumeThread可能会使用同步功能或像同步功能一样执行内存屏障操作,但文档中没有指定它执行同步功能,因此最好假设它不执行同步功能

    参考资料:

    关于32位和64位对齐类型的原子性

    在“易失性”上,包括内存围栏

    它用于确保读取/写入整个值

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

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

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

    这是最低要求,处理器通常提供额外的处理器,使简单操作原子化。喜欢