以前我们说过在一些简单的例子中,比如为一个字段赋值或递增该字段,我们需要对线程进行同步,
虽然lock可以满足我们的需要,但是一个竞争锁一定会导致阻塞,然后忍受线程上下文切换和调度的开销,在一些高并发和性能比较关键的地方,这些是不能忍受的。
.net framework 提供了非阻塞同步构造,为一些简单的操作提高了性能,它甚至都没有阻塞,暂停,和等待线程。
Memory Barriers and Volatility (内存栅栏和易失字段 )
考虑下下面的代码:
回答是“yes”,基于以下原因:
编译器,clr 或 cpu 可能会为了性能而重新为程序的指令进行排序,例如可能会将方法A中的两句代码的顺序进行调整。
编译器,clr 或 cpu 可能会为变量的赋值采用缓存策略,这样这些变量就不会立即对其他变量可见了,例如方法A中的变量赋值,不会立即刷新到内存中,变量B看到的变量并不是最新的值。
C# 和运行时非常小心的保证这些优化策略不会影响正常的单线程的代码和在多线程环境下加锁的代码。
除此之外,你必须显示的通过创建内存屏障(Memory fences) 来限制指令重新排序和读写缓存对程序造成的影响。
Full fences:
最简单的完全栅栏的方法莫过于使用Thread.MemoryBarrier方法了。
以下是msdn的解释:
Thread.MemoryBarrier: 按如下方式同步内存访问:执行当前线程的处理器在对指令重新排序时,不能采用先执行 MemoryBarrier 调用之后的内存访问,再执行 MemoryBarrier 调用之前的内存访问的方式。
按照我个人的理解:就是写完数据之后,调用MemoryBarrier,数据就会立即刷新,另外在读取数据之前调用MemoryBarrier可以确保读取的数据是最新的,并且处理器对MemoryBarrier的优化小心处理。
C# Lock 语句(Monitor.Enter / Monitor.Exit)
在Interlocked类的所有方法。
使用线程池的异步回调,包括异步的委托,APM 回调,和 Task continuations.
在一个信号构造中的发送(Settings)和等待(waiting)
你不需要对每一个变量的读写都使用完全栅栏,假设你有三个answer 字段,我们仍然可以使用4个栅栏。例如:
</div&g