详解C#多线程之线程同步

2019-12-30 16:02:53王振洲

在懂得多线程同步问题的同学们都会知道如果用两个线程分别去执行上面两个方法时,得出的结果有两个:

1.不输出任何东西;

2.输出5。但是在CSC编译器编译成IL语言或JIT编译成机器语言的过程中,会进行代码优化,在方法Thread1中,编译器会觉得给两个字段赋值会没什么所谓,它只会站在单个线程执行的角度来看,完全不会顾及多线程的问题,因此它有可能会把两行代码的执行顺序调乱,导致先给mFlag赋值为1,再给mValue赋值为5,这就导致了第三种结果,输出0。可惜这种结果我一直无法测试出来。

解决这个现象的就是volatile构造,使用了这种构造的效果是,凡是对使用了此构造的字段进行读操作时,该操作都保证在原有代码顺序下会在最先执行;或者是凡是对使用了此构造的字段进行写操作时,该操作都保证在原有代码顺序下会在最后执行。

实现了volatile的构造现在来说有三个,其一是Thread的两个静态方法VolatileRead和VolatileWrite,在MSND上的解析如下

Thread.VolatileRead 读取字段值。 无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。

Thread.VolatileWrite 立即向字段写入一个值,以使该值对计算机中的所有处理器都可见。

在多处理器系统上, VolatileRead 获得由任何处理器写入的内存位置的最新值。 这可能需要刷新处理器缓存;VolatileWrite 确保写入内存位置的值立即可见的所有处理器。 这可能需要刷新处理器缓存。

即使在单处理器系统上, VolatileRead 和 VolatileWrite 确保值为读取或写入内存,并不缓存 (例如,在处理器寄存器中)。 因此,您可以使用它们可以由另一个线程,或通过硬件更新的字段对访问进行同步。

从上面的文字看不出他和代码优化有任何关联,那接着往下看。

volatile关键字则是volatile构造的另外一种实现方式,它是VolatileRead和VolatileWrite的简化版,使用 volatile 修饰符对字段可以保证对该字段的所有访问都使用 VolatileRead 或 VolatileWrite。MSDN中对volatile关键字的说明是

volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

从这里可以看出跟代码优化有关系了。而纵观上面的介绍得出两个结论:

1.使用了volatile构造的字段读写都是直接对内存操作,不涉及CPU寄存器,使得所有线程对它的读写都是同步,不存在脏读了。读操作是原子的,写操作也是原子的。

2.使用了volatile构造修饰(或访问)字段,它会严格按照代码编写的顺序执行,读操作将会在最早执行,写操作将会最迟执行。