假设一个类有一个公共int计数器字段,可以被多个线程访问。这个int值只能自增或自减。

要增加这个字段,应该使用哪种方法,为什么?

锁(this.locker) this.counter + +; 联锁。增量(ref this.counter); 将counter的访问修饰符更改为public volatile。

现在我已经发现了volatile,我已经删除了许多lock语句和Interlocked的使用。但有理由不这么做吗?


当前回答

我想补充一下其他答案中提到的volatile、Interlocked和lock之间的区别:

volatile关键字可以应用于以下类型的字段:

引用类型。 指针类型(在不安全的上下文中)。注意,虽然指针本身可以是volatile的,但它所指向的对象却不能。在其他 总之,你不能声明一个“指针”为“volatile”。 简单类型,如sbyte、byte、short、ushort、int、uint、char、float和bool。 具有以下基类型之一的枚举类型:byte、sbyte、short、ushort、int或uint。 泛型类型参数称为引用类型。 IntPtr和UIntPtr。

其他类型,包括double和long,不能标记为“volatile” 因为不能保证对这些类型的字段的读写 成为原子。来保护对这些类型的多线程访问 字段,使用互锁类成员或使用 锁的声明。

其他回答

我只是在这里指出Orion Edwards回答中关于volatile的错误。

他说:

“如果它是不稳定的,这只是确保两个cpu看到相同的数据 同样的时间。”

这是错误的。在微软关于volatile的文档中,提到:

在多处理器系统上,volatile读操作不会 保证获得写入该内存位置的最新值 任何处理器。类似地,volatile写操作不会 保证写入的值对其他人立即可见 处理器。”

互锁的函数不锁。它们是原子的,这意味着它们可以在递增过程中不需要上下文切换的情况下完成。因此不存在死锁或等待的机会。

我想说的是,你应该总是更喜欢它,而不是锁和增量。

如果你需要在一个线程中写入,然后在另一个线程中读取,或者你想让优化器不对变量的操作重新排序(因为事情发生在另一个线程中,而优化器不知道),Volatile是有用的。这是一个与增量正交的选择。

如果您想了解更多关于无锁代码的内容,以及编写无锁代码的正确方法,这是一篇非常好的文章

http://www.ddj.com/hpc-high-performance-computing/210604448

我赞同Jon Skeet的回答,并想为所有想了解“volatile”和“Interlocked”的人添加以下链接:

原子性,波动性和不可变性是不同的,第一部分- (Eric Lippert的神话般的冒险在编码)

原子性、挥发性和不变性是不同的,第二部分

原子性、挥发性和不变性是不同的,第三部分

Sayonara Volatile - (Wayback Machine 2012年乔·达菲的博客快照)

编辑:正如评论中提到的,这些天我很高兴在单个变量的情况下使用Interlocked,这显然是可以的。当它变得更复杂时,我仍然会恢复到锁定…

当你需要增量操作时,使用volatile是没有用的——因为读和写是分开的指令。另一个线程可以在读取后但在回写之前更改值。

就我个人而言,我几乎总是锁定——以一种明显正确的方式获得正确比波动性或interlocking . increment更容易。就我而言,无锁多线程是为真正的线程专家准备的,而我不是。如果Joe Duffy和他的团队构建了很好的库,可以并行化,而不像我构建的那样有太多的锁,那就太棒了,我马上就会使用它——但是当我自己做线程时,我会尽量保持简单。

我想补充一下其他答案中提到的volatile、Interlocked和lock之间的区别:

volatile关键字可以应用于以下类型的字段:

引用类型。 指针类型(在不安全的上下文中)。注意,虽然指针本身可以是volatile的,但它所指向的对象却不能。在其他 总之,你不能声明一个“指针”为“volatile”。 简单类型,如sbyte、byte、short、ushort、int、uint、char、float和bool。 具有以下基类型之一的枚举类型:byte、sbyte、short、ushort、int或uint。 泛型类型参数称为引用类型。 IntPtr和UIntPtr。

其他类型,包括double和long,不能标记为“volatile” 因为不能保证对这些类型的字段的读写 成为原子。来保护对这些类型的多线程访问 字段,使用互锁类成员或使用 锁的声明。