代码之家  ›  专栏  ›  技术社区  ›  Hongli

在不锁定的情况下读取正在并发修改的整数变量是否安全?

  •  51
  • Hongli  · 技术社区  · 16 年前

    假设我在一个类中有一个整数变量,这个变量可能会被其他线程并发修改。写入受互斥体保护。我也需要保护阅读吗?我听说有一些硬件架构,如果一个线程修改了一个变量,而另一个线程读取了它,那么读取的结果将是垃圾;在这种情况下,我确实需要保护阅读。但我从未见过这样的建筑。

    这个问题假设一个事务只包含更新一个整数变量,所以我不担心事务中可能涉及的任何其他变量的状态。

    12 回复  |  直到 16 年前
        1
  •  36
  •   peterchen    16 年前

    原子读数


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

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

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

    MSDN: Memory and Synchronization issues , MemoryBarrier

    [编辑]另请参阅drhirsch的评论。

        2
  •  18
  •   Gunther Piez    16 年前

    你问了一个关于读取变量的问题,然后你谈到了更新变量,这意味着一个读-修改-写操作。

    如果是原子操作 对于几乎所有架构,整数都是如此。

    • int的大小大于指令的自然大小,例如在8位架构上使用16位int。
        3
  •  9
  •   Dmitry    16 年前

    我建议不要依赖任何编译器或架构 在这种情况下。

        4
  •  5
  •   NomeN    16 年前

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

    共识是,您应该单独封装/锁定所有写入,而读取可以与(仅)其他读取同时执行

        5
  •  4
  •   Stack Overflow is garbage    16 年前

    假设我在一个类中有一个整数变量,这个变量可能会被其他线程并发修改。写入受互斥体保护。我也需要保护阅读吗?我听说有一些硬件架构,如果一个线程修改了一个变量,而另一个线程读取了它,那么读取的结果将是垃圾;在这种情况下,我确实需要保护阅读。但我从未见过这样的建筑。

    建筑。每种架构都有读与写并发会导致垃圾的情况。 然而,几乎每种架构都有例外。

    当前的 即使另一个线程正在写入,也可以将值作为单个原子操作。所以对于整数,你是安全的 大多数 建筑。有些人也会将这种保证扩展到其他一些尺寸,但这显然取决于硬件。

    对于非字长变量,读取和写入通常都是非原子的,必须通过其他方式同步。

        6
  •  3
  •   Vitaly Dyatlov    16 年前


    您可以在不使用互斥体的情况下读写整数变量。这是因为整数是32位架构中的基类型,每次修改/读取值都是通过一次操作完成的。


    myvar++;
    

        7
  •  2
  •   iain    16 年前

    虽然在没有同步的情况下在32位系统上读取int可能是安全的。我不会冒险。虽然多个并发读取不是问题,但我不喜欢写入和读取同时发生。

    如果你有一个不错的线程库,比如boost.thread或zthread,那么你应该有读/写锁。这些将非常适合您的情况,因为它们允许多次读取,同时保护写入。

        8
  •  1
  •   starblue    16 年前

    这可能发生在使用16位整数的8位系统上。

    如果你想避免锁定,在适当的情况下,你可以多次读取,直到得到两个相等的连续值。例如,我使用这种方法在32位嵌入式目标上读取64位时钟,其中时钟滴答被实现为中断例程。在这种情况下,读取三次就足够了,因为时钟在读取例程运行的短时间内只能滴答一次。

        9
  •  1
  •   harrymc    16 年前

    一般来说,每条机器指令在执行时都会经过几个硬件阶段。由于大多数当前的CPU都是多核或超线程的,这意味着读取变量可能会启动它在指令管道中的移动,但它不会阻止另一个CPU核心或超线程并发执行到同一地址的存储指令。读取和存储这两条同时执行的指令可能会“交叉路径”,这意味着读取将在存储新值之前收到旧值。

        10
  •  0
  •   SadSido    16 年前

    对具有并发性的变量的读/写都必须受到关键部分(而不是互斥)的保护。除非你想浪费一整天的时间调试。

    我认为,关键部分是特定于平台的。在Win32上,临界区非常高效:当没有发生联锁时,进入临界区几乎是免费的,不会影响整体性能。当发生联锁时,它仍然比互斥更有效,因为它在挂起线程之前实现了一系列检查。

        11
  •  -1
  •   LaszloG    16 年前
        12
  •  -3
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    在盲目使用volatile之前,先了解一下它的作用: http://msdn.microsoft.com/en-us/library/12a04hfd(VS.80).aspx