Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用联锁的线程安全日期时间更新*_C#_Multithreading - Fatal编程技术网

C# 使用联锁的线程安全日期时间更新*

C# 使用联锁的线程安全日期时间更新*,c#,multithreading,C#,Multithreading,我可以使用Interlocked.*同步方法更新DateTime变量吗 我希望在内存中保留最后一次触摸的时间戳。多个http线程将更新last touch DateTime变量 我理解DateTime变量是被替换而不是更新的值类型 我能想到的最好办法是将时间戳作为总滴答数保存在长时间内 class x { long _lastHit; void Touch() { Interlocked.Exchange( ref _lastHit, DateTime.Now.Ticks

我可以使用Interlocked.*同步方法更新DateTime变量吗

我希望在内存中保留最后一次触摸的时间戳。多个http线程将更新last touch DateTime变量

我理解DateTime变量是被替换而不是更新的值类型

我能想到的最好办法是将时间戳作为总滴答数保存在长时间内

class x
{
  long _lastHit;

  void Touch()
  {
    Interlocked.Exchange( ref _lastHit, DateTime.Now.Ticks );   
  }
}

是的,你能做到。您最大的问题可能是DateTime.Ticks的分辨率只有约20毫秒。因此,您是否保留
DateTime last
长Ticks
变量并不重要。但是,由于DateTime没有过度的Exchange,您需要使用
long

编辑:基于@romkyns下面的评论[谢谢]

如果您的代码在32位计算机上运行。然后,一个64位的长度将通过两个原子操作写入内存,可以通过上下文切换中断。 所以一般来说,你确实需要处理这个问题

但要明确的是,对于这个特定场景,(写一个代表时间刻度的长值),可以说这个问题非常不可能,不值得处理。。。因为(除了每2^32个刻度一次的分秒外),高位字(32位)中的值对于任何两个并发写入都是相同的。。。即使在非常不可能的情况下,有两个并发写入跨越该边界,它们并发地相互中断,并且从一个获取hi字,从另一个获取low字,除非您也每千秒读取一次该值,否则下一次写入将修复该问题,并且不会造成任何伤害。然而,采用这种方法,不管坏情况发生的可能性有多大,仍然允许出现极为微弱但可能出现的情况,即每40亿个滴答声中就有一个错误的值。。。(祝你好运尝试复制这个bug…)


如果您运行在64位机器上,otoh(此时更可能是这样,但不能保证),那么64位内存插槽中的值是以原子方式写入的,您不需要担心这里的并发性。只有当某个程序不变量在某个处理块期间处于无效状态且可能被另一个线程中断时,才可能出现争用条件(这是您试图阻止的)。如果您所做的只是写入lastTouch DateTime变量(内存位置),那么就没有需要关注的invlay不变量,因此您不必担心并发访问。

选项1:使用
长的
互锁。这不需要
volatile
(事实上,如果你有它,你会得到一个警告),因为联锁已经确保了原子更新。您可以通过这种方式获得日期时间的确切值

long _lastHit;

void Touch()
{
    Interlocked.Exchange(ref _lastHit, DateTime.Now.ToBinary());
}
要以原子的方式阅读此内容:

DateTime GetLastHit()
{
    long lastHit = Interlocked.CompareExchange(ref _lastHit, 0, 0);
    return DateTime.FromBinary(lastHit);
}
这将返回_lastHit的值,如果为0,则将其与0交换(即,除了以原子方式读取该值外,不执行任何操作)

简单地读取是不好的——至少是因为变量没有标记为volatile,所以后续读取可能只是重用缓存的值。结合volatile和Interlocked可能在这里起作用(我不完全确定,但我认为即使另一个内核执行非联锁读取,也无法在不一致的状态下看到联锁写入)。但如果你这样做,你会得到一个警告和一个结合两种不同技术的代码气味

选项2:使用锁。在这种情况下不太理想,因为在这种情况下,联锁方法的性能更高。但您可以存储正确的类型,而且更清晰一些:

DateTime _lastHit;
object _lock = new object();

void Touch()
{
    lock (_lock)
        _lastHit = DateTime.Now;
}
您也必须使用锁来读取此值!顺便说一句,除了互斥之外,锁还确保缓存的值不能被看到,读/写也不能被重新排序

非选项:无论您是否将其标记为
易失性
,都不执行任何操作(只需写入值)。这是错误的-即使您从未读取该值,您在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

我的方法不是最好的方法之一,但您可以使用字符串变量存储格式化的日期,然后将其解析回datetime:

class x
{
  string _lastHit;

  void Touch()
  {
    Interlocked.Exchange( ref _lastHit, DateTime.Now.ToString("format your date") );   
  }
}
当需要使用此值时,只需解析为DateTime:

DateTime.Parse(_lastHit)

解析总是有效的,因为字符串是使用
DateTime
类格式化的,但是您可以使用
TryParse
来处理可能的解析错误

@Adam-正如我在删除您的答案之前所说的。感谢您提到volatile关键字,这是我知识上的一个空白。但是从文档来看,它似乎只提供线程安全读取,而不提供更新。@在这种情况下,Henk-20ms是可以的。触摸时间戳的用例是多租户web系统。凌晨2点,在运行数据修复脚本之前,我需要检查数据库实例上托管的所有用户是否已退出系统。如果您希望上次更新更准确,可以使用system.Diagnostics.Stopwatch.GetTimestamp方法,或者使用DateTime的另一个属性。。。请参阅我的答案。更正:DateTime.Ticks可以以100ns的增量存储时间(非常接近64位计数器)。然而,如果你从DateTime.Now开始阅读,它以20毫秒的跳跃速度递增,这意味着它比秒表类要精确得多(然而,秒表类大约每小时漂移几秒钟,这意味着它在测量更长的时间段时精度较低——这是另一回事)。关于并发性和逻辑,您是对的,但是编写
长的
不是原子的,所以您确实需要锁定或互锁。@Charles-正如Henk所说,我关心的是读取部分更新的长值。我很欣赏一种极不可能的情况。camelCase,“极不可能”只是意味着更难找到和调试,但CLR规范规定了“一致性”