Eri*_*ert 23
我的同事和我正在讨论使用C#.NET 4.0在英特尔架构上读取双精度的原子性.
英特尔保证,当与8字节边界对齐时,8字节双精度在读取和写入时是原子的.
C#不保证 double与8字节边界对齐.
他认为我们应该使用
Interlocked.Exchange
方法写入double,但只是读取double值(在其他一些线程中)保证是原子的.
你的同事没有仔细考虑这个问题.对于其他互锁操作,互锁操作仅是原子操作.在某些时候使用互锁操作没有任何意义; 这好比说,即通过路口向北去交通不必遵守交通灯,因为是通过路口去西部交通不遵守交通灯.每个人都必须遵守灯光以避免碰撞; 你不能只做一半.
我的论点是.NET不保证这种原子性,但他的论点是在英特尔架构上这是有保证的(可能不在AMD,SPARC等).
看,假设参数是正确的,它不是.我们应该在这里得出的结论是,通过做错而节省的几纳秒在某种程度上值得冒险吗?忘了互锁. 每次都要完全锁定.在线程之间共享内存时,唯一不应该完全锁定的情况是,当您遇到已证明的性能问题时,实际上是由于锁的12纳秒开销.那就是当你的程序中最慢的12纳秒罚款并且仍然是不可接受的时候,那就是你应该考虑使用低锁解决方案的那一天.你的程序中最慢的东西是12纳秒无竞争锁?没有?然后,停下来有这样的说法,并花你宝贵的时间,使你的程序是采取部分更超过12纳秒的速度更快.
Reader可以读取陈旧(前一个)值,但不能读取不正确的值(写入前后的部分读取给出垃圾值).
不要将原子性与波动性混为一谈.
互锁操作和锁定语句都将形成内存屏障,以确保读取或发布最新值.不需要普通的非易失性读或写操作; 如果碰巧这样做,你很幸运.
如果您对这些问题感兴趣,我偶尔会询问的相关问题是在什么情况下可以省略对整数访问的锁定.我关于这个主题的文章是:
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位值进行任何访问.