如何确保时间戳始终是唯一的

本文关键字:唯一 时间戳 何确保 确保 | 更新日期: 2023-09-27 17:49:40

我使用时间戳对程序中的并发更改进行临时排序,并要求每个更改的时间戳是唯一的。然而,我发现仅仅调用DateTime.Now是不够的,因为如果快速连续调用,它通常会返回相同的值。

我有一些想法,但没有一个让我觉得是"最好"的解决方案。我是否可以编写一种方法来保证每次后续调用都产生唯一的DateTime ?

我是否应该使用一个不同的类型,也许是一个长整型?DateTime有一个明显的优点,它很容易被解释为一个实时的,不像,比如说,增量计数器。

Update:这是我最终编码的一个简单的折衷解决方案,仍然允许我使用DateTime作为我的时间键,同时确保每次调用方法时的唯一性:

private static long _lastTime; // records the 64-bit tick value of the last time
private static object _timeLock = new object();
internal static DateTime GetCurrentTime() {
    lock ( _timeLock ) { // prevent concurrent access to ensure uniqueness
        DateTime result = DateTime.UtcNow;
        if ( result.Ticks <= _lastTime )
            result = new DateTime( _lastTime + 1 );
        _lastTime = result.Ticks;
        return result;
    }
}

由于每个tick值仅为千万分之一秒,因此该方法仅在每秒调用1000万次(顺便说一下,它的执行效率足够高)时才会引入明显的时钟偏差,这意味着它完全可以用于我的目的。

下面是一些测试代码:
DateTime start = DateTime.UtcNow;
DateTime prev = Kernel.GetCurrentTime();
Debug.WriteLine( "Start time : " + start.TimeOfDay );
Debug.WriteLine( "Start value: " + prev.TimeOfDay );
for ( int i = 0; i < 10000000; i++ ) {
    var now = Kernel.GetCurrentTime();
    Debug.Assert( now > prev ); // no failures here!
    prev = now;
}
DateTime end = DateTime.UtcNow;
Debug.WriteLine( "End time:    " + end.TimeOfDay );
Debug.WriteLine( "End value:   " + prev.TimeOfDay );
Debug.WriteLine( "Skew:        " + ( prev - end ) );
Debug.WriteLine( "GetCurrentTime test completed in: " + ( end - start ) );

…结果:

Start time:  15:44:07.3405024
Start value: 15:44:07.3405024
End time:    15:44:07.8355307
End value:   15:44:08.3417124
Skew:        00:00:00.5061817
GetCurrentTime test completed in: 00:00:00.4950283
所以换句话说,在半秒内,它生成了1000万个唯一的时间戳,最终结果只提前了半秒。在实际应用程序中,这种倾斜是不明显的。

如何确保时间戳始终是唯一的

获得严格升序且没有重复的时间戳序列的一种方法是以下代码:

与这里的其他答案相比,这个答案有以下好处:

  1. 该值与实际实时值密切相关(除非在极端情况下,当请求率非常高时,它们会略微超过实时)。
  2. 它是无锁的,应该比使用lock语句的解决方案性能更好。
  3. 它保证升序(简单地附加一个循环计数器不能)。

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long original, newValue;
           do
           {
               original = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newValue = Math.Max(now, original + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newValue, original) != original);
           return newValue;
       }
   }
}

还要注意下面的注释,因为64位读操作在32位系统上不是原子的,所以应该使用original = Interlocked.Read(ref lastTimestamp);

嗯,你的问题的答案是"你不能",因为如果两个操作同时发生(它们在多核处理器中会发生),它们将具有相同的时间戳,无论你设法收集多少精度。

也就是说,听起来你想要的是某种自动递增的线程安全计数器。要实现这一点(假设作为全局服务,可能在静态类中),您将使用Interlocked.Increment方法,如果您决定需要超过int.MaxValue可能的版本,也可以使用Interlocked.Read .

DateTime.Now每10-15ms更新一次。

本身不是一个副本,但是这个线程有一些关于减少重复/提供更好的时间分辨率的想法:

如何在。net/c#中获得滴答精度的时间戳?

话虽这么说:时间戳是非常糟糕的信息键;如果事情发生得如此之快,您可能需要一个索引/计数器来保持项目出现时的离散顺序。这里没有歧义

我发现最简单的方法是将时间戳和原子计数器结合起来。您已经知道时间戳的低分辨率问题。单独使用原子计数器也存在一个简单的问题,即如果要停止和启动应用程序,则需要存储其状态(否则计数器将从0开始,从而导致重复)。

如果您只是想要一个唯一的id,那么将时间戳和计数器值用分隔符连接起来就很简单了。但是因为你希望这些值总是有序的,这是不够的。基本上,您所需要做的就是使用原子计数器值为时间戳添加固定宽度精度。我是一名Java开发人员,所以我将无法提供c#示例代码,但问题是在这两个领域是相同的。因此,只需遵循以下一般步骤:

  1. 您将需要一个方法来为您提供从0-99999循环的计数器值。100000是将毫秒精度时间戳与64位长的固定宽度值连接时可能的最大值数。因此,您基本上假设在单个时间戳分辨率(15毫秒左右)内永远不需要超过100000个id。使用Interlocked类提供原子递增和重置为0的静态方法是理想的方法。
  2. 现在要生成id,只需将时间戳与计数器值连接到5个字符。因此,如果时间戳是13023991070123,计数器是234,那么id就是1302399107012300234。

只要您不需要超过6666/ms的id(假设15ms是您最精细的分辨率),该策略就可以工作,并且在重新启动应用程序时始终无需保存任何状态。

不能保证是唯一的,但是也许使用tick足够细粒度吗?

一个刻度代表100纳秒或千万分之一秒第二。a里有10000个蜱虫毫秒。

不确定你想做什么,但也许可以考虑使用队列来处理顺序处理记录。