易失与互锁与锁定

假设一个类有一个public int counter字段,可以被多个线程访问。 这个int只是递增或递减。

为了增加这个领域,应该使用哪种方法,为什么?

  • lock(this.locker) this.counter++;
  • Interlocked.Increment(ref this.counter);
  • counter的访问修饰符更改为public volatile
  • 现在我发现了volatile ,我一直在删除很多lock语句和Interlocked的使用。 但是有没有理由不这样做?


    最差(实际上不会工作)

    counter的访问修饰符更改为public volatile

    正如其他人所提到的,这本身并不安全。 volatile的一点是多个CPU上运行的多个线程可以缓存数据并重新排序指令。

    如果它不是 volatile ,并且CPU A增加一个值,那么CPU B可能实际上看不到增加的值,直到一段时间后,这可能会导致问题。

    如果它是volatile ,这只能确保两个CPU同时看到相同的数据。 它不会阻止它们交错读取和写入操作,这是您试图避免的问题。

    次好的:

    lock(this.locker) this.counter++ ;

    这是安全的(只要你记得在其他地方lock你访问this.counter )。 它阻止任何其他线程执行任何其他由locker保护的代码。 使用锁也可以防止多CPU重新排序问题,这很好。

    问题是,锁定速度很慢,如果你在其他地方重新使用locker ,那么你可能会无缘无故地阻塞其他线程。

    最好

    Interlocked.Increment(ref this.counter);

    这是安全的,因为它有效地进行了阅读,增加和写入“一击”,不能被打断。 正因为如此,它不会影响任何其他代码,也不需要记住在其他地方锁定。 它也非常快(正如MSDN所说,在现代CPU上,这通常是单个CPU指令)。

    但我不完全确定,如果它绕过其他CPU重新排序,或者你还需要将volatile与增量结合起来。

    InterlockedNotes:

  • 对于任何数量的核心或CPU,互锁方法都是安全的。
  • 互锁方法对他们执行的指令应用完整的栅栏,所以重新排序不会发生。
  • 互锁方法不需要甚至不支持对易失性字段的访问 ,因为易失性是在给定字段的操作周围放置一半围栏,并且互锁使用全隔离。
  • 脚注:什么波动是真正有益的。

    由于volatile不能防止这种多线程问题,它有什么作用? 一个很好的例子就是说你有两个线程,一个总是写入一个变量(比如queueLength ),另一个总是读取同一个变量。

    如果queueLength不是易失性的,则线程A可能会写入五次,但线程B可能会将这些写入视为延迟(甚至可能以错误的顺序)。

    解决办法是锁定,但在这种情况下你也可以使用volatile。 这将确保线程B将始终看到线程A写入的最新内容。 但请注意,如果您有从未阅读的作家,以及从不写作的读者,以及您正在撰写的内容是原子值,则此逻辑才有效。 只要您执行单个读取 - 修改 - 写入,您需要进入联锁操作或使用锁定。


    编辑:正如评论中指出的那样,现在我很乐意使用Interlocked来处理单个变量的情况,它显然是可以的。 当它变得更复杂时,我仍然会恢复锁定...

    当您需要增加时,使用volatile将无济于事,因为读取和写入是单独的指令。 读完之后但在写回之前,另一个线程可能会更改此值。

    就个人而言,我几乎总是锁定 - 以一种明显正确的方式比波动性或互锁增长更容易。 就我而言,无锁多线程适用于真正的线程专家,我不是其中一员。 如果Joe Duffy和他的团队构建了很好的库,这些库能够并行处理事务,而不会像构建的东西那样锁定,那就太棒了,我会用心跳 - 但是当我自己执行线程时,我会尝试把事情简单化。


    volatile ”不会取代Interlocked.Increment ! 它只是确保变量不被缓存,但直接使用。

    增加一个变量实际上需要三个操作:

  • 增量
  • Interlocked.Increment执行所有三个部分作为一个单一的原子操作。

    链接地址: http://www.djcxy.com/p/91967.html

    上一篇: Volatile vs. Interlocked vs. lock

    下一篇: The code example which can prove "volatile" declare should be used