在英特尔架构上双读原子?

Alo*_*lok 29 .net c# intel

我的同事和我正在讨论使用C#.NET 4.0在英特尔架构上读取双精度的原子性.他认为我们应该使用Interlocked.Exchange方法写入a double,但只是读取double值(在其他一些线程中)保证是原子的.我的论点是.NET不保证这种原子性,但他的论点是在英特尔架构上这是有保证的(可能不在AMD,SPARC等).

任何英特尔和.NET专家都会分享一些亮点吗?

Reader可以读取陈旧(前一个)值,但不能读取不正确的值(写入前后的部分读取给出垃圾值).

Eri*_*ert 23

我的同事和我正在讨论使用C#.NET 4.0在英特尔架构上读取双精度的原子性.

英特尔保证,当与8字节边界对齐时,8字节双精度在读取和写入是原子的.

C#不保证 double与8字节边界对齐.

他认为我们应该使用Interlocked.Exchange方法写入double,但只是读取double值(在其他一些线程中)保证是原子的.

你的同事没有仔细考虑这个问题.对于其他互锁操作,互锁操作仅是原子操作.在某些时候使用互锁操作没有任何意义; 这好比说,即通过路口向北去交通不必遵守交通灯,因为是通过路口去西部交通遵守交通灯.每个人都必须遵守灯光以避免碰撞; 你不能只做一半.

我的论点是.NET不保证这种原子性,但他的论点是在英特尔架构上这是有保证的(可能不在AMD,SPARC等).

看,假设参数是正确的,它不是.我们应该在这里得出的结论是,通过做错而节省的几纳秒在某种程度上值得冒险吗?忘了互锁. 每次都要完全锁定.在线程之间共享内存时,唯一应该完全锁定的情况是,当您遇到已证明的性能问题时,实际上是由于锁的12纳秒开销.那就是当你的程序中最慢的12纳秒罚款并且仍然是不可接受的时候,那就是你应该考虑使用低锁解决方案的那一天.你的程序中最慢的东西是12纳秒无竞争锁?没有?然后,停下来有这样的说法,并花你宝贵的时间,使你的程序是采取部分超过12纳秒的速度更快.

Reader可以读取陈旧(前一个)值,但不能读取不正确的值(写入前后的部分读取给出垃圾值).

不要将原子性与波动性混为一谈.

互锁操作和锁定语句都将形成内存屏障,以确保读取或发布最新值.不需要普通的非易失性读或写操作; 如果碰巧这样做,你很幸运.

如果您对这些问题感兴趣,我偶尔会询问的相关问题是在什么情况下可以省略对整数访问的锁定.我关于这个主题的文章是:

  • @supercat:你对争用提出了一个很好的观点,这就是为什么我小心翼翼地说出惩罚是针对无争用的锁定的原因.争论怎么样?**如果您的性能问题是由于过多的争用,那么这就是在架构级别修复的问题**,而不是通过低锁定解决方案.如果你有交通堵塞,那么*设计一个更有效的道路网络*,不要开始运行红灯并希望最好. (7认同)
  • 锁定的开销超过了获取和释放锁所需的12ns.使用`Interlocked.Increment`的方法可以合理地保证它将在一定的时间内返回.使用`lock`的代码不能:如果其他东西获得了锁,然后得到了waylaid,它可能会永远等待等待.让子系统使用`Interlocked`而不是锁,在实际中,可以允许它在诸如`Finalize`方法之类的上下文中安全地调用,其中代码不应该在任意时间段内阻塞. (4认同)
  • @LucaCremonesi:我看过很多次这段代码*:`int DecreaseCounter(){InterlockedDecrement(ref counter); if(counter == 0)Cleanup(); 返回柜台; }`.一旦计数器为零,没有人会再次减少,我们希望`Cleanup`能够*完全*运行一次.你看到了缺陷吗? (2认同)
  • @LucaCremonesi:正确.我给的代码有两个缺陷.首先,如果计数器为2,两个线程进入,减少,现在计数器为零,两个线程都调用`Cleanup`,这可能不是线程安全的,因为它假定它只被调用一次.其次,并且危险性较小,两个不同线程上的两个调用可以返回相同的值,这似乎是意外的.您的修复是正确的,并且是标准模式.但是,我的观点是,如果你每次共享内存时总是一直使用锁,那么你不必担心这些东西. (2认同)

cod*_*eim 20

是的,不是.在32位处理器上,它不保证原子,因为double大于本机的单词大小.在64位处理器上,正确对齐的访问是原子的.64位CLI保证64位读取的所有内容都是原子的.所以你需要为x64(而不是任何CPU)构建程序集.否则,如果你的程序集可以在32位上运行,你最好使用Interlocked,看看Atomicity,volatile和immutability是不同的,第二部分是Eric Lippert.我认为你可以依靠Eric Lippert对Microsoft CLR的了解.

ECMA CLI标准也支持这一点,即使C#本身并不能保证:

符合标准的CLI应保证对正确对齐的内存位置的读写访问权限不大于本机字大小(native int类型的大小)是原子的(参见§I.12.6.2)

它还说,在操作是原子的处理器上,Interlocked方法通常被编译为单个指令,因此在我的书中使用它没有性能损失.另一方面,如果不应该使用它可能会有更严重的惩罚.

另一个相关的Stack Overflow问题是什么操作在C#中是原子的?.


And*_*bel 18

如果它们在8字节地址边界上对齐,则读取或写入双精度在英特尔架构上是原子的.请参阅Is Updating double operation atomic.

尽管在英特尔架构上的.NET代码中读取和写入双打可能实际上是原子的,但我不相信它,因为C#规范并不能保证它,请参阅Eric Lippert的答案.

以下数据类型的读取和写入是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型.此外,在先前列表中具有基础类型的枚举类型的读取和写入也是原子的.其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不保证是原子的.

使用Interlocked的读取和写入是安全的.它保证了原子性.在默认情况下是原子的体系结构中,它不应产生任何开销.您需要使用Interlocked读取和写入来确保不读取部分写入的值(文档引用InterLocked.Read()):

只有在System.IntPtr为64位长的系统上,Read方法和Increment,Decrement和Add方法的64位重载才是真正原子的.在其他系统上,这些方法相互之间是原子的,但与其他访问数据的方法无关.因此,要在32位系统上保持线程安全,必须通过Interlocked类的成员对64位值进行任何访问.