如何解决如何正确读取Interlocked.Increment\'ed int字段?
| 假设我有一个非易失性int字段,以及一个Interlocked.Increment
的线程。另一个线程可以安全地直接读取此内容,还是读取也需要互锁?
我以前认为我必须使用互锁读取来确保我看到的是当前值,因为毕竟该字段不是可变的。我一直用ѭ1来实现。
但是,我偶然发现了这个答案,这表明实际上纯读总是会看到version0值的当前版本,并且由于int读取已经是原子的,因此不需要做任何特殊的事情。我还找到了一个请求,Microsoft在其中拒绝了对Interlocked.Read(ref int)的请求,进一步表明这是完全多余的。
那么我真的可以安全地读取没有such4ѭ的int
字段的最新值吗?
解决方法
如果要保证另一个线程将读取最新值,则必须使用
Thread.VolatileRead()
。 (*)
读取操作本身是原子性的,因此不会造成任何问题,但是如果没有volatile读取,则可能会从缓存中获取旧值,或者编译器可能会优化代码并完全消除读取操作。从编译器的角度来看,代码在单线程环境中工作就足够了。易失性操作和内存障碍用于限制编译器优化和重新排序代码的能力。
有几个参与者可以更改代码:编译器,JIT编译器和CPU。哪一个表明您的代码已损坏并不重要。唯一重要的是.NET内存模型,因为它指定了所有参与者必须遵守的规则。
(*)Thread.VolatileRead()
并未真正获得最新值。它将读取该值,并在读取后添加一个内存屏障。第一个易失性读取可能会获取缓存的值,而第二个易失性读取可能会获取更新的值,因为如果需要,第一个易失性读取的内存屏障已强制进行缓存更新。实际上,编写代码时,此细节并不重要。
,有一点元问题,但是关于使用ѭ7的一个好方面(忽略了明显的缺点,即阅读时很难理解)是无论works3还是ѭ9都可以使用。的确,int
读取始终是原子的,但是long
读取不是或可能不是,这取决于体系结构。不幸的是,只有当value
的类型为long
时,Interlocked.Read(ref value)
才有效。
考虑到您以int
字段开头的情况,这使得不可能使用Interlocked.Read()
,因此您将直接读取该值,因为无论如何这都是原子的。但是,在以后的开发中,您或其他人决定需要ѭ9-编译器不会警告您,但现在您可能会遇到一个细微的错误:不再保证读取访问权限是原子的。我发现在这里使用Interlocked.CompareExchange()
是最好的选择。根据底层处理器指令,它可能会更慢,但从长远来看,它会更安全。我对ѭ5的内部知识还不够了解。关于此用例,它可能会“更好”,因为它提供了更多的签名。
我不会尝试在循环或任何紧的方法调用中直接读取值(即没有上述任何机制),因为即使写入是易失性和/或内存障碍,也没有什么告诉编译器该字段的值实际上可以在两次读取之间改变。因此,该字段应为volatile
或应使用任何给定的构造。
我的两分钱。
,您是正确的,您不需要特殊的指令即可自动读取32位整数,但是,这意味着您将获得\“ whole \”值(即,您不会获得部分写入和部分写入)另一个)。您无法保证一旦阅读该值就不会更改。
此时,您需要确定是否需要使用其他同步方法来控制访问,例如是否要使用此值从数组中读取成员,等等。
简而言之,原子性可确保操作完全且不可分割地进行。给定某些操作A
包含N
步骤,如果您在A
之后立即执行该操作,则可以确保所有ѭ22happened步骤均与并发操作隔离发生。
如果您有两个线程执行了原子操作A
,那么可以保证您只会看到两个线程之一的完整结果。如果要协调线程,则可以使用原子操作来创建所需的同步。但是原子操作本身并不能提供更高级别的同步。 Interlocked
系列方法可用于提供一些基本的原子操作。
同步是一种更广泛的并发控制,通常围绕原子操作构建。大多数处理器都包括内存屏障,可让您确保所有高速缓存行都被清空并且您拥有一致的内存视图。易失性读取是确保对给定存储位置进行一致访问的一种方法。
尽管不是立即适用于您的问题,但阅读有关数据库的ACID(原子性,一致性,隔离性和持久性)可能会帮助您使用术语。
,是的,您阅读的所有内容都是正确的。 Interlocked.Increment的目的是使对字段进行更改时正常读取不会为假。读字段不是危险的,写字段是危险的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。