读取一个没有锁定同时修改的整数变量是否安全?

Hon*_*gli 46 c++ concurrency multithreading

假设我在类中有一个整数变量,并且该变量可能被其他线程同时修改.写入受互斥锁保护.我是否也需要保护读取?我听说有一些硬件架构,如果一个线程修改一个变量,另一个线程读取它,那么读取结果将是垃圾; 在这种情况下,我确实需要保护读取.我从未见过这样的架构.

这个问题假设单个事务只包含更新单个整数变量,因此我不担心可能也涉及事务的任何其他变量的状态.

pet*_*hen 34

原子读
如前所述,它依赖于平台.在x86上,该值必须在4字节边界上对齐.通常,对于大多数平台,读取必须在单个CPU指令中执行.

优化程序缓存
优化程序不知道您正在读取由其他线程修改的值.声明该值volatile有助于:优化器将为每次访问发出内存读/写,而不是试图将值缓存在寄存器中.

CPU缓存
尽管如此,您可能会读取过时的值,因为在现代体系结构中,您有多个具有单独缓存且未自动保持同步的内核.您需要一个读取内存屏障,通常是特定于平台的指令.

在Wintel上,线程同步功能会自动添加完整的内存屏障,或者您可以使用InterlockedXxxx函数.

MSDN:内存和同步问题,MemoryBarrier

[编辑]也请看drhirsch的评论.

  • -1表示CPU缓存问题完全错误.谷歌MESI协议.内存障碍适用于弱排序加载/存储指令(在x86上通常是流式传输mmx),这是一个完全不同的主题. (9认同)
  • +1因为内存屏障/ CPU缓存提及以及优化器缓存,这里没有人似乎承认. (8认同)
  • 在这种情况下,"指令重新排序"并不真正相关,这是处理器可以执行的本地优化.如果您不使用同步并在其中一个处理器中执行写操作,则结果将是未定义的. - MSDN实际上不是查找低级指令的正确位置,更好地检查英特尔或AMD的手册或您正在使用的任何内容. (6认同)
  • (1)是的.没有多处理器架构没有缓存一致性,仅仅因为它是必需的.除了MESI之外还有其他协议,但这是最普遍的.(2)您似乎混淆了指令的重新排序,内存操作的重新排序以及对相同数据的并发访问.这是完全不同的.在大多数架构上,指令和相应的存储器操作没有耦合,处理器可以在某些限制内对存储器操作重新排序以提高效率(无序架构).其中一个罕见的反例是Intel Atom. (5认同)
  • 但这通常与程序员无关,除非您使用具有"弱内存排序"的指令.您需要汇编程序指令或内在函数来使用它们,因此如果您使用的是高级语言,则永远不必使用内存屏障.到目前为止,这绝对与多处理器及其缓存无关.如果现在两个处理器访问位于其中一个处理器高速缓存中的相同数据,则硬件确保两个处理器都获得相同的数据,甚至可以从一个处理器高速缓存转移到另一个处理器高速缓存. (5认同)

Gun*_*iez 15

您询问有关读取变量的问题,然后讨论更新变量,这意味着读取 - 修改 - 写入操作.

假设你真的是指前者,那么如果它是原子操作,则读取是安全的.对于几乎所有的体系结构,这对整数来说都是正确的.

有一些(和罕见的)例外:

  • 读取未对齐,例如访问奇数地址处的4字节int.通常,您需要强制编译器使用特殊属性来进行一些错位.
  • int的大小大于指令的自然大小,例如在8位体系结构上使用16位整数.
  • 一些架构具有人为限制的总线宽度.我只知道很老的和过时的,比如386sx或68008.

  • 不。阅读有关 MESI 协议的信息。正常读/写操作的缓存一致性始终得到保证并且对软件透明。 (2认同)
  • 下一部分6讨论CPU缓存.无论如何,我碰巧知道有多个CPU的系统没有硬件缓存一致性.也许你从未见过一个,但它们确实存在.如果正确的硬件指令不与共享数据一起使用,您将*会遇到严重的错误.我引用该系列的第6部分的另一个引用:"但是一旦你摆脱了围绕一条公共总线构建的系统,即使读写也保持相同的相对顺序,也很难确定." (2认同)

Dmi*_*try 8

在这种情况下,我建议不要依赖任何编译器或体系结构.
每当你有一群读者和作家(而不仅仅是读者或只是作家)时,你最好将它们全部同步.想象一下,你的代码运行的是一个人的心脏,你真的不希望它读错了值,你肯定不希望你所在城市的电厂变得"沸腾",因为有人决定不使用那个互斥锁.从长远来看,让自己保持睡眠,同步他们.
如果你只有一个线程读取 - 你只使用那个互斥锁是好的,但是如果你计划多个读者和多个编写者,你需要一个复杂的代码来同步它.我还没有看到一个很好的读/写锁实现,它也是"公平的".

  • 这是一个技术问题; 它需要一个技术答案. (2认同)

Nom*_*meN 5

想象一下,您正在一个线程中读取变量,该线程在读取时被中断,并且该变量由写入线程更改.现在读取线程恢复后读取整数的值是多少?

除非读取变量是原子操作,在这种情况下只需要一条(汇编)指令,否则无法确保上述情况不会发生.(该变量可以写入内存,检索该值将需要多条指令)

一致意见是你应该单独封装/锁定所有写入,而读取可以与(仅)其他读取同时执行