使用Interlocked进行线程安全的DateTime更新。*
我可以使用Interlocked。*同步方法来更新DateTime变量吗?
我希望在内存中保留最后触摸时间戳。 多个http线程将更新最后一次触摸DateTime变量。
我很欣赏DateTime变量是被替换而不是更新的值类型。
我能想到的最好的方法是将时间戳保持为长整数
class x { long _lastHit; void Touch() { Interlocked.Exchange( ref _lastHit, DateTime.Now.Ticks ); } }
是的,你可以这样做。 你最大的问题可能是DateTime.Ticks只有大约20毫秒的分辨率。 因此,如果您保留DateTime last
或long ticks
变量并不重要。 但由于DateTime没有Exchange重载,因此需要使用long
。
选项1 :使用带有Interlocked
和DateTime.ToBinary()
的long
。 这不需要volatile
(事实上,如果你有它,你会收到警告)因为Interlocked已经确保了primefaces更新。 您可以通过这种方式获得DateTime的确切值。
long _lastHit; void Touch() { Interlocked.Exchange(ref _lastHit, DateTime.Now.ToBinary()); }
以primefaces方式阅读:
DateTime GetLastHit() { long lastHit = Interlocked.CompareExchange(ref _lastHit, 0, 0); return DateTime.FromBinary(lastHit); }
这将返回_lastHit的值,如果为0,则将其与0交换(即除了primefaces读取值之外什么都不做)。
简单地读取是不好的 – 至少因为变量没有标记为volatile,所以后续读取可能只是重用缓存的值。 将volatile和Interlocked组合在一起可能会起作用(我不完全确定,但我认为即使另一个内核执行非互锁读取,也不能在不一致的状态下看到互锁写入)。 但是,如果你这样做,你会得到一个警告和代码气味,结合两种不同的技术。
选项2 :使用锁。 在这种情况下不太理想,因为在这种情况下,互锁方法更具性能。 但是你可以存储正确的类型,而且它更清晰:
DateTime _lastHit; object _lock = new object(); void Touch() { lock (_lock) _lastHit = DateTime.Now; }
你必须使用锁来读取这个值! 顺便提一下,除了互斥之外,锁还确保不能看到缓存的值并且不能重新排序读/写。
非选项 :不执行任何操作(只需写入值),无论是否将其标记为volatile
。 这是错误的 – 即使您从未读过该值,您在32位计算机上的写入可能会以如此不幸的方式交错,从而导致损坏的值:
Thread1: writes dword 1 of value 1 Thread2: writes dword 1 of value 2 Thread2: writes dword 2 of value 2 Thread1: writes dword 2 of value 1 Result: dword 1 is for value 2, while dword 2 is for value 1
编辑:根据@romkyns下面的评论[谢谢]
如果您的代码在32位计算机上运行。 然后在两个primefaces操作中将64位长写入内存,这可以被上下文切换中断。 所以一般来说你需要处理这个问题。
但要明确的是,对于这个特定的场景,(写一个代表时间刻度的长值),可以说这个问题是如此非常无情,以至于不值得处理…因为(除了每一秒一瞬间)无论如何,高位字(32位)中的值对于任何两个并发写入都是相同的…甚至在极不可能的事件中,有两个并发写入跨越该边界,这同时中断彼此,你从一个得到hi字,从另一个得到低字,除非你每隔millesecond也读这个值,下一次写将无论如何都会解决问题,并且不会造成任何伤害。 然而,采用这种方法,无论坏的情况多么不可能,仍然允许极其微小但可能的情况下,每隔40亿个滴答中就有一个错误的值……(并且好运试图重现那个错误…)
如果你在64位机器上运行,otoh(更可能在这个时间点但不能保证)那么64位内存插槽中的值是primefaces地写的,你不需要担心这里的并发性。 竞争条件(这是您要阻止的)只有在某些程序不变的情况下才会发生,该程序不变状态在某些处理块中可能被另一个线程中断。 如果你所做的只是写入lastTouch DateTime变量(内存位置),那么就没有这样的invlaid不变量,因此你不必担心并发访问。
我的方法不是最好的方法之一,但您可以使用字符串var来存储格式化日期,然后将其解析回datetime:
class x { string _lastHit; void Touch() { Interlocked.Exchange( ref _lastHit, DateTime.Now.ToString("format your date") ); } }
当您需要使用此值时,只需解析为DateTime:
DateTime.Parse(_lastHit)
解析始终有效,因为字符串是使用DateTime
类格式化的,但您可以使用TryParse
来处理可能的解析错误