internal sealed class ThreadSharingData{
private Int32 m_flag =0;
private Int32 m_value =0;
//由一个线程执行
public void Thread1()
{
// 注意:以下两行代码可以按相反的顺序执行
m_value =5;
m_flag =1;
}
// 这个方法由另一个线程执行
public void Thread2()
{
// 注意:m_value 可能在m_flag之前读取
if(m_flag == 1)
{
Console.WriteLine(m_value);
}
}
}
上述代码的问题在于,编译器/CPU 在解释代码的时候,可能反转Thread1方法中的两行代码。毕竟,反转两行代码不会改变方法的意图。方法需要在m_value中存储5,在m_flag中存储1. 从单线程应用程序的角度来说,这梁行代码的执行顺序无关紧要。如果这两行代码真的按相反顺序执行。执行Thread2方法的另一个线程可能看到m_flag是1, 并显示0
下面从另一个角度研究上述代码。假定Thread1方法中的代码按照程序顺序执行。编译Thread2方法中的代码时,编译器必须生成代码将m_flag和m_value从RAM读入CPU寄存器。RAM可能先传递m_value的值,它包含一个0值。然后,Thread1方法可能执行,将m_value 更改为5,将m_flag更改为1 但是,Thread2的cpu 寄存器没有看到m_value已被另一个线程更改为5. 然后m_flag的值从RAM读入CPU寄存器。由于m_flag已变成1,所以造成Thread2同时显示0
这些细微之处很容易被人忽视。由于调试版本不会进行优化,所以等到程序生成发行版本的时候,这些问题才会显现出来,造成很难检测这些问题并纠正代码。现在,研究一下如何纠正自己的代码。
System.Threading.Thread 类提供了三个静态方法,如下所示:
public sealed class Thread {
public static void VolatileWrite(ref Int32 address, Int32 value);
public static Int32 VolatileRead(ref Int32 address);
public static void MeoryBarrier();
}
这些方法比较特殊。事实上,这些方法会禁用C#编译器、JIT编译器和CPU自身所执行一些优化。下面描述了方法时如何工作的。
1, VolatileWrite 方法强迫address 中的值在调用时写入。除此以外,按照程序顺序,在之前的加载和存储操作必须在调用VolatileWrite之前发生。
2,VolatileRead 方法强迫address中的值在调用时读取。除此之外,按照程序顺序,在之后加载和存储操作必须在调用VolatileRead之后发生。
3,MemoryBarrier方法不访问内存,它强迫按照程序顺序,之前加载和存储操作在调用MemoryBarrier之前完成。榆次同时,之后的加载和存储操作在调用MemoryBarrier之后完成。和另外两个方法相比,MemoryBarrier的作用小得多。
internal sealed class ThreadSharingData{
private Int32 m_flag =0;
private Int32 m_value =0;
//由一个线程执行
public void Thread1()
{
// 注意: 在将1写入m_flag之前,必须先将5写入m_value
m_value =5;
Thread.VolatileWrite(ref m_flag, 1);
}
// 这个方法由另一个线程执行
public void Thread2()
{
// 注意:m_value 必须在读取了m_flag之后读取
if(Thread.VolatileRead(ref m_flag) == 1)
{
Console.WriteLine(m_value);
}
}
}
以上代码还可以用volatile 关键字 改写
internal sealed class ThreadSharingData{
private volatile Int32 m_flag =0;
private Int32 m_value =0;
//由一个线程执行
public void Thread1()
{
// 将1写入之前,必须先写入m_value
m_value =5;
m_flag =1;
}
// 这个方法由另一个线程执行
public void Thread2()
{
// 注意:m_value 必须m_flag之后读取
if(m_flag == 1)
{
Console.WriteLine(m_value);
}
}
}