从MS的字来看,对doubles的读取和写入(以及其他操作(不是原子的,因此不是线程安全的。我想看看是否可以通过装箱双值并使对装箱值的引用变为volatile来确保对双线程的读写安全。下面是一个类,演示了我在工作线程和消费者线程中使用盒装双打:
public class Program {
public volatile static object objCounter;
public static AutoResetEvent Finish = new AutoResetEvent(false);
static void Main(string[] args) {
// Worker thread that writes to the boxed double values
Task.Run(() => {
double dCounter = 0d;
while (!Finish.WaitOne(100, false)) {
dCounter += 1.5d;
// Box dCounter into objCounter -
// Copy double value to new location on the heap
// and point the objCounter to this location on the heap
objCounter = (object)dCounter;
}
});
// Consumer thread that reads the boxed double values
for (int index = 0; index < 50; index++) {
Console.WriteLine(String.Format("Double counter value = {0}",objCounter));
Thread.Sleep(100);
}
Finish.Set();
}
}
我相信上面的代码是线程安全的,原因如下:
当工作线程将double写入
objCounter
时,它必须在堆上创建一个新位置,将double值复制到堆上的那个新位置,然后将引用原子性地复制到objCounter
的新堆位置。引用(指针(到objCounter
的这个原子拷贝保证是直接从MS的口开始的原子拷贝:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/variables#96-变量引用的原子性-(请参阅9.6变量引用的atomicity(需要注意的是,每次我们框选double时,都会在堆上创建一个新位置,如果我们不在堆上新建位置,这将是线程安全的。由于
objCounter
是易失性的,因此在指向堆上包含当前双值的内存位置的引用的工作线程和使用者线程之间不会存在内存可见性问题。
我是否正确地认为我在上面指定的工作线程和消费者线程中使用盒装双打是线程安全的
objCounter
是volatile
字段,下面的两行:
objCounter = (object)dCounter;
Console.WriteLine(objCounter);
相当于:
Volatile.Write(ref objCounter, (object)dCounter);
Console.WriteLine(Volatile.Read(ref objCounter));
两种Volatile
方法都会创建内存屏障。Volatile.Write
确保处理器在该调用之后不会移动与创建objCounter
实例相关的指令。Volatile.Read
确保处理器在该调用之前不会移动与在控制台中显示objCounter
相关的指令。因此,显示objCounter
的线程将始终看到一个完全初始化的实例。