使用 Interlocked 进行线程安全的日期时间更新。*
我可以使用 Interlocked.*
同步方法来更新 DateTime
变量吗?
我希望在内存中保留最后一次接触的时间戳。多个http线程将更新最后一个触摸DateTime
变量。
我很欣赏 DateTime
变量是被替换而不是更新的值类型。
我能想到的最好的方法是将时间戳保存为很长一段时间内的总滴答数
class x
{
long _lastHit;
void Touch()
{
Interlocked.Exchange(ref _lastHit, DateTime.Now.Ticks);
}
}
Can I use an Interlocked.*
synchronization method to update a DateTime
variable?
I wish to maintain a last-touch time stamp in memory. Multiple http threads will update the last touch DateTime
variable.
I appreciate that DateTime
variables are value types that are replaced rather than updated.
The best I can come up with is to hold the timestamp as total ticks in a long
class x
{
long _lastHit;
void Touch()
{
Interlocked.Exchange(ref _lastHit, DateTime.Now.Ticks);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
选项 1:使用
long
与Interlocked
和DateTime.ToBinary()
。这不需要易失性
(事实上,如果有它,您会收到警告),因为 Interlocked 已经确保了原子更新。您可以通过这种方式获得 DateTime 的准确值。要以原子方式读取此内容:
这将返回 _lastHit 的值,如果它是 0,则将其与 0 交换(即除了以原子方式读取该值之外,不执行任何操作)。
简单地读取是不行的 - 至少因为变量没有标记为易失性,因此后续读取可能只是重用缓存的值。结合挥发性和互锁可能在这里工作(我不完全确定,但我认为即使另一个核心执行非互锁读取,也无法在不一致的状态下看到互锁写入)。但如果您这样做,您将收到警告和组合两种不同技术的代码味道。
选项 2:使用锁。在这种情况下不太理想,因为互锁方法在这种情况下性能更高。但是您可以存储正确的类型,并且稍微清楚一点:
您也必须使用锁来读取此值!顺便说一句,除了互斥之外,锁还可以确保无法看到缓存的值并且无法重新排序读/写。
非选项:什么都不做(只写值),无论您是否将其标记为
易失性
。这是错误的 - 即使您从未读取过该值,您在 32 位机器上的写入也可能会以一种不幸的方式交错,从而导致您得到损坏的值:Option 1: use a
long
withInterlocked
andDateTime.ToBinary()
. This doesn't needvolatile
(in fact you'd get a warning if you had it) because Interlocked already ensures an atomic update. You get the exact value of the DateTime this way.To read this atomically:
This returns the value of _lastHit, and if it was 0 swaps it with 0 (i.e. does nothing other than read the value atomically).
Simply reading is no good - at a minimum because the variable isn't marked as volatile, so subsequent reads may just reuse a cached value. Combining volatile & Interlocked would possibly work here (I'm not entirely sure, but I think an interlocked write cannot be seen in an inconsistent state even by another core doing a non-interlocked read). But if you do this you'll get a warning and a code smell for combining two different techniques.
Option 2: use a lock. Less desirable in this situation because the Interlocked approach is more performant in this case. But you can store the correct type, and it's marginally clearer:
You must use a lock to read this value too! Incidentally, besides mutual exclusion a lock also ensures that cached values can't be seen and reads/writes can't be reordered.
Non-option: do nothing (just write the value), whether you mark it as
volatile
or not. This is wrong - even if you never read the value, your writes on a 32 bit machine may interleave in such an unlucky way that you get a corrupted value:是的,你可以这样做。您最大的问题可能是 DateTime.Ticks 的分辨率只有 ~20 毫秒。因此,保留
DateTime last
或longticks
变量并不重要。但由于 Exchange 没有对 DateTime 进行重载,因此您需要使用long
。Yes, you can do this. Your biggest problem may be that DateTime.Ticks only has a resolution of ~20 ms. So it doesn't really matter if you keep a
DateTime last
or along ticks
variable. But since there is no overload of Exchange for DateTime, you need to uselong
.使用新的
Unsafe
类,终于可以直接在DateTime
字段上使用:备注(警告):这里的等式与 DateTime 的常规等式不同:
(日期时间 x, 日期时间 y) => x.Ticks == y.Ticks;
(DateTime x, DateTime y) =>; x.Ticks == y.Ticks && x.Kind == y.Kind;
这里的相等性比较了
DateTime
的所有位,常规的忽略了专用于Kind
的位。With the new
Unsafe
class it is finally possible directly on aDateTime
field:REMARK (WARNING): the equality here is different from the regular one of the DateTime:
(DateTime x, DateTime y) => x.Ticks == y.Ticks;
(DateTime x, DateTime y) => x.Ticks == y.Ticks && x.Kind == y.Kind;
Equality here compares all bits of the
DateTime
, regular one omits bits dedicated to theKind
.编辑:基于@romkyns 的以下评论[谢谢]
如果您的代码在 32 位计算机上运行。那么 64 位长的数据将通过两个原子操作写入内存,可以被上下文切换中断。
所以一般来说你确实需要处理这个问题。
但要明确的是,对于这个特定的场景,(写一个代表时间滴答的长值)可以说这个问题是如此非常不值得处理......因为(除了每 2^32 滴答一次的瞬间),无论如何,任何两个并发写入的高位字(32 位)中的值都将相同......即使在极不可能的情况下,< /em> 跨越该边界的两个并发写入,它们同时互相中断,您从一个获得高字,从另一个获得低字,除非您也每毫秒读取该值,否则下一次写入无论如何都会解决问题,并且不会造成任何伤害。然而,采用这种方法,无论坏情况发生的可能性有多大,仍然允许出现极其微小但可能的情况,即每 40 亿个时钟周期中就会出现一次错误的值......(祝您尝试重现该错误好运...)
如果您在 64 位机器上运行,otoh(此时更有可能,但不保证),则 64 位内存插槽中的值将被原子写入,并且这里你不需要担心并发问题。仅当某些程序不变量在某些处理块期间处于无效状态时,才会发生竞争条件(这是您试图阻止的情况)被另一个线程中断。如果您所做的只是写入 lastTouch DateTime 变量(内存位置),那么就没有需要关心的 invlaid 不变量,因此您无需担心并发访问。
EDIT: based on comments below from @romkyns [Thanks]
If your code is running on a 32 bit machine. then a 64 bit long is going to be written to memory in two atomic operations, which can be interrupted by a context switch.
So in general you do need to deal with this issue.
But to be clear, for this specific scenario, (writing a long value which represents time ticks) it could be argued that the problem is so very unlilely as to be not worth dealing with... since (except for a split second once every 2^32 ticks), the value in the high word (32 bits) will be the same for any two concurrent writes anyway... and even in the very unlikely event that there are two concurrent writes which span that boundary, which concurrently interrupt each other and you get the hi word from one and the low word from the other, unless you are also reading this value every millesecond, the next write will fix the issue anyway, and no harm would be done. Taking this approach, however, no matter how unlikely the bad case might be, still allows for the extremely slim but possible scenario of gettign a wrong value in there once in every 4 Billion ticks... (And good luck trying to reproduce that bug...)
If you are running on a 64 bit machine, otoh, (much more likely at this point in time but not guaranteed) then the value in the 64 bit memory slot is written atomically, and you don't need to worry about concurrency here. A race condition (Which is what you are trying to prevent) can only occur if there is some program invariant that is in an invalid state during some block of processing that can be interrupted by another thread. If all you are doing is writing to the lastTouch DateTime variable (memory location) then there is no such invlaid invariant to be concerned with, and therefore you do not need to worry about concurrent access.
我的方法不是最好的方法之一,但是您可以使用字符串 var 来存储格式化的日期,然后将其解析回 datetime:
当您需要使用此值时,只需解析为 DateTime:
解析始终有效,因为字符串使用
DateTime
类进行格式化,但您可以使用TryParse
来处理可能的解析错误My approach is not one of the best, but you can use a string var to store a formatted date and then parse it back into datetime:
When you need to use this value, just parse into DateTime:
The parsing is always working, because the string is formatted using the
DateTime
class, but you can useTryParse
to handle possible parsing errors