【发布时间】:2018-10-29 10:33:58
【问题描述】:
我可以想到一些用例,在这些用例中,让 DateTime 对象成为原子对象会非常有用。从语言设计的角度来看,不使 DateTime 易变的优点是什么?
【问题讨论】:
-
长期使用锁是唯一可靠的方法。 Volatile 仅关闭可能导致其他问题的编译器优化。在多任务甚至多线程场景中遇到的问题。 DateTime 可能一开始就没有那些编译器优化。所以关闭它们是没有意义的。
我可以想到一些用例,在这些用例中,让 DateTime 对象成为原子对象会非常有用。从语言设计的角度来看,不使 DateTime 易变的优点是什么?
【问题讨论】:
volatile 关键字不保证原子性。 Atomicity is guaranteed for all data types of 32-bits or fewer,带或不带关键字。
volatile 关键字的目的是确保所有线程都在查看变量的同一个副本。您可能会惊讶地发现副本可能不止一个:它们可以保存在主内存中,正如您所期望的那样,也可以保存在一个或多个级别的 CPU 缓存或 CPU 寄存器中。
当您使用volatile 关键字时,编译器会发出一些额外的指令(“memory barriers”)并跳过某些优化以确保所有线程都会看到相同的变量副本。就是这样。
对于大于 32 位的类型,无论如何您都需要 lock,而锁则需要 automatically put those memory barriers in place。所以这些变量不需要 volatile ;用lock 包围访问变量的代码。
【讨论】:
DateTime,时代(你称之为“时刻 X”)不是 Unix 时代。 The documentation 很清楚,它从 0001 年 1 月 1 日开始测量。
volatile 关键字只能应用于可原子更新的字段,例如 int、long 引用类型等(请参阅docs 了解更多信息)。
获得所需行为的一种方法是lock statements。这将确保一次只有一个线程可以进入代码的“锁定”部分(也称为关键部分)。
【讨论】:
System.Int64 (long) 是不能使用的类型之一。
IsVolatile 并让运行时从那里获取它。
有许多编译器和 JiT 编译器优化。例如,如果你写:
{
int temp = Int32.Parse(input);
Console.WriteLine(temp);
}
JiT 可能会在调试构建之外思考:“嘿,int 变量实际上并没有在其他任何地方使用。我可以像这样适当地编译它”:
Console.WriteLine(Int32.Parse(input));
它会得到完全相同的结果。”
当人们有这样的嵌套调用时,我经常建议使用临时变量将它们分开,因为我非常清楚 JiT 可以(并且可能会)处理它们。此更改不会对性能产生相关影响,但会提高可读性和可调试性。
编译器也可以反其道而行之。例如,如果您两次调用同一个索引,它实际上会尝试通过添加一个临时变量来保存索引绑定检查。当您期望一个新值时,您实际上可能正在使用本地副本。
但是对于多任务和多线程,任何这样的优化都可能会给您带来很多额外的麻烦。 volatile 会关闭这些优化。但它只能这样做,前提是该类型一开始就有这种优化。
【讨论】: