为了简单起见,我们假设我们有 8 个线程和一个 8 字节长度的字节数组。每个线程都从该数组中分配一个字节 - 也就是说,线程可以自由修改分配的字节,而不能修改数组中的任何其他字节。
我们还假设数组在 8 字节边界上对齐。
乍一看,让线程随意修改它们的(并且仅是它们的)字节是线程安全的,因为这里实际上没有共享数据。但是,据我了解,当前所有运行 64 位 Windows 的 Intel 和 AMD 处理器只能读取和写入不少于 8 个字节(64 位)一次。因此,我假设当仅修改 8 字节对齐块中的 1 个字节时,CPU 会读取所有 8 个字节,修改相关字节,然后将 1 个已修改字节与 7 个未修改字节一起写回。这不是线程安全的,所以我怀疑直接写入这些字节时需要一个 LOCK 前缀。 虽然我真的希望我是错的。 有什么想法吗?
lock
这是一个足够基本的概念,实际上很难在官方文档中找到明确的说明。但例如,在《英特尔软件开发人员手册》第 3A 卷第 8.1.1 节中,有一条声明称读取或写入字节“始终以原子方式执行”。这包括通过普通的
mov
进行加载和存储,甚至不能采用
lock
前缀。问题仅与 x86 相关,但这对于每个现代架构都是如此(DEC Alpha 是最近值得注意的例外)。为了支持 C11/C++11 内存模型,它实际上至关重要,该模型指定对不同对象(包括同一数组的不同字节)的并发访问独立发生,并且不会导致数据争用。
但是 - 据我了解 - 所有当前运行 64 位 Windows 的 Intel 和 AMD 处理器只能读写不少于 8 个字节(64 位)一次。Micro从架构上来说(即在指定指令的软件可观察行为的级别),这根本不是真的。从软件的角度来看,所有 x86 机器都可以读写单个字节,而不会以任何方式干扰其他内核对其他字节的访问。
架构上(即在硬件实现细节层面),核心读取和写入的数据实际上甚至大于 64 位 - 它是整个缓存行(通常为 64 字节)。然而,缓存一致性协议(例如MESI)可以确保两个内核永远不会对同一缓存行进行冲突的写入。 因此,如果核心 A 尝试写入给定缓存行的字节 0,而核心 B 同时尝试写入字节 1,则其中一个将首先将缓存行保持在独占状态;假设它是核心 A。A 将在 B 等待时进行写入。之后,修改后的缓存行(包括字节0的更新值)将被传输到核心B,因为它获得了该行的所有权。然后它可以写入字节 1。稍后获取缓存行的任何核心都将看到两个字节的更新版本,并且其中一个字节不可能永久丢失,否则您可能会担心。