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

我可以避免对很少变化的变量使用锁吗?

  •  11
  • JMarsch  · 技术社区  · 15 年前

    我一直在读Joe Duffy关于并发编程的书。我有一个关于无锁线程的学术问题。

    不过,我有一个问题: 假设我有一个带有int属性的类。

    多个线程将非常频繁地读取此属性引用的值

    值会发生更改的情况极为罕见,当值发生更改时,只有一个线程会对其进行更改。

    如果在另一个使用它的操作运行时它确实发生了变化,那么没有人会失去一根手指(使用它的人做的第一件事就是将它复制到局部变量)

    我可以使用锁(或者readerwriterlockslim来保持读取的并发性)。 我可以标记变量volatile(很多这样做的示例)

    然而,即使是volatile也会对性能造成影响。

    如果我在VolatileWrite发生变化时使用它,并让读取保持正常访问,该怎么办。大概是这样的:

    public class MyClass
    {
      private int _TheProperty;
      internal int TheProperty
      {
        get { return _TheProperty; }
        set { System.Threading.Thread.VolatileWrite(ref _TheProperty, value); }
      }
    }
    

    7 回复  |  直到 13 年前
        1
  •  6
  •   Eric Lippert    15 年前

    将变量标记为“volatile”有两种效果。

    1) 读和写具有获取和释放语义,因此其他内存位置的读和写不会相对于该内存位置的读和写“在时间上向前和向后移动”(这是一种简化,但你接受我的观点。)

    2) 抖动生成的代码不会“缓存”逻辑上似乎不变的值。

    但在我看来,后一点似乎非常相关。如果在非易失性变量上有自旋锁:

    while(this.prop == 0) {}
    

    jitter在其权限范围内生成此代码,就像您编写的代码一样

    if (this.prop == 0) { while (true) {} }
    

    不管它是否真的这样做,我不知道,但它有权利这样做。如果您希望代码在循环的每一次循环中实际重新检查属性,那么将其标记为volatile是正确的方法。

        2
  •  4
  •   Jon Skeet    15 年前

    问题是阅读线程是否会 曾经 看到变化了吗。这不仅仅是它是否看到它的问题 立即 .

    坦率地说,我已经放弃了试图理解波动性——我知道这并不完全是我过去认为的那样。。。但我也知道,如果在阅读过程中没有任何记忆障碍,你可能永远都在阅读相同的旧数据。

        3
  •  2
  •   Anon.    15 年前

    volatile 是因为编译器现在生成代码来实际检查值,而不是优化值——换句话说,您将 不管你做什么,都要承受那次表演的打击。

        4
  •  2
  •   tony    15 年前

    最后 请参阅对内存地址的更改。即使没有锁或内存障碍。锁和屏障只会确保它以相对顺序(w.r.t其他指令)发生,这样它对您的程序来说似乎是正确的。

    问题不在于缓存一致性(我希望Joe Duffy的书不会犯这个错误)。缓存保持一致性——只是这需要时间,处理器不必等待这种情况发生——除非您强制执行。因此,处理器将继续执行下一条指令,这可能会发生,也可能不会发生 之前 因为

    因此,读取可能看起来很旧或来自过时的缓存,但实际上它发生的时间比预期的要早(通常是因为前瞻和分支预测)。当它真的 读,缓存是一致的,从那时起它就改变了。因此,当您阅读它时,该值并不陈旧,但当您需要它时,它就已经存在了。你只是读得太快了-(

    或者两者兼而有之。

    最后

    在C#中,我对CLR了解不够,无法知道一个值可以在寄存器中保留多长时间,也无法确保从内存中获得真正的重读。您已经失去了“弱”易失性。

    但我看不到任何保证。如果您可以将易失性读取限制在代码中的某个特定点,该点通常是,但不太经常(即在一段时间内开始下一个任务(things_to_do)循环),那么这可能是您所能做的最好方法。

        5
  •  1
  •   codekaizen    15 年前

    volatile 关键字,但在Jeffery Richter的代码示例中看到此模式后,我开始使用它。

        6
  •  1
  •   Oliver Seiler    15 年前

    被缓存中有内存的其他CPU看到)。在这方面,volatile将有助于确保优化器不会通过读取寄存器中缓存的值来优化内存访问(无论如何都要通过缓存)。C#文档似乎很清楚这一点。同样,应用程序程序员通常不必自己处理缓存一致性问题。

    我强烈建议阅读免费提供的论文《每个程序员都应该了解内存》。很多魔法在引擎盖下进行,主要是防止射中自己的脚。

        7
  •  0
  •   John Gietzen    15 年前

    在C#中 int

    因为您说过只有一个线程向它写入,所以您永远不应该对正确的值有争用,并且只要您正在缓存本地副本,就永远不会得到脏数据。

    volatile 如果操作系统线程将执行更新。

    还要记住,有些 操作 不是 原子的 bool 如果有多个编写器,则类型不会损坏,如下语句:

    a = !a;