Rom*_*kov 56 .net c# multithreading thread-safety
假设我有一个非易失性的int字段,以及一个它Interlocked.Increment的线程.另一个线程可以直接安全地读取它,还是读取也需要互锁?
我以前认为我必须使用互锁读取来保证我看到当前值,因为毕竟,该字段不是易失性的.我一直在Interlocked.CompareExchange(int, 0, 0)努力实现这一目标.
但是,我偶然发现了这个答案,这表明实际的普通读取总会看到Interlocked.Incremented值的当前版本,并且因为int读取已经是原子的,所以不需要做任何特殊的事情.我还发现了Microsoft拒绝Interlocked.Read(ref int)请求的请求,进一步表明这完全是多余的.
那么我能真正安全地阅读这样一个int领域的最新价值Interlocked吗?
mgr*_*ber 19
如果要保证其他线程将读取最新值,则必须使用Thread.VolatileRead().(*)
读取操作本身是原子的,因此不会导致任何问题,但是如果没有易失性读取,您可能会从缓存中获得旧值,或者编译器可能会优化您的代码并完全消除读取操作.从编译器的角度来看,代码在单线程环境中工作就足够了.易失性操作和内存屏障用于限制编译器优化和重新排序代码的能力.
有几个参与者可以改变代码:编译器,JIT编译器和CPU.它们中哪一个显示您的代码被破坏并不重要.唯一重要的是.NET内存模型,因为它指定了所有参与者必须遵守的规则.
(*)Thread.VolatileRead()并没有真正得到最新的价值.它将读取该值并在读取后添加内存屏障.第一个易失性读取可能会获得缓存值,但第二个将获得更新值,因为第一个易失性读取的内存屏障在必要时强制执行缓存更新.实际上,在编写代码时,这个细节并不重要.
sun*_*ide 14
元问题的一点,而是用良好的方面Interlocked.CompareExchange(ref value, 0, 0)(忽略了明显的缺点是它很难用于读取时,理解)是它的工作原理,无论int或long.确实,int读取总是原子的,但long读取不是或可能不是,这取决于体系结构.不幸的是,Interlocked.Read(ref value)只有value类型才有效long.
考虑一下你从一个int字段开始的情况,这使得它无法使用Interlocked.Read(),因此你将直接读取该值,因为无论如何它都是原子的.但是,在开发后期,您或其他人决定需要a long- 编译器不会警告您,但现在您可能会有一个微妙的错误:读取访问不再保证是原子的.我发现Interlocked.CompareExchange()在这里使用最好的选择; 根据底层处理器指令,它可能会更慢,但从长远来看它更安全.我对内部的了解不够Thread.VolatileRead(); 关于这个用例可能"更好",因为它提供了更多的签名.
我不会尝试在循环或任何紧密方法调用中直接读取值(即没有任何上述机制),因为即使写入是易失性和/或内存屏障,也没有任何东西告诉编译器字段的值实际上可以在两次读取之间变化.因此,该字段应该是或者应该使用volatile任何给定的结构.
我的两分钱.
你是正确的,你不需要一个特殊的指令来原子地读取32位整数,但是,这意味着你将得到"整个"值(即你不会得到一个写入的一部分和另一个写入的一部分).您无法保证在阅读后该值不会发生变化.
此时您需要决定是否需要使用其他同步方法来控制访问,例如,如果您使用此值来从数组中读取成员等.
简而言之,原子性确保操作完全和不可分割地发生.给定一些A包含N步骤的操作,如果您A确定所有N步骤都是在与并发操作隔离的情况下发生的,那么您可以立即执行操作.
如果您有两个执行原子操作的线程,A那么您将保证只能看到两个线程之一的完整结果.如果要协调线程,可以使用原子操作来创建所需的同步.但原子操作本身并不能提供更高级别的同步.该方法的家人都提供给提供一些基本的原子操作.Interlocked
同步是一种更广泛的并发控制,通常围绕原子操作构建.大多数处理器都包含内存屏障,允许您确保刷新所有缓存行,并且您具有一致的内存视图.易失性读取是确保对给定内存位置的一致访问的一种方法.
虽然不能立即应用于您的问题,但阅读有关数据库的ACID(原子性,一致性,隔离性和持久性)可能会帮助您使用术语.
| 归档时间: |
|
| 查看次数: |
14689 次 |
| 最近记录: |