【问题标题】:How to ensure a timestamp is always unique?如何确保时间戳始终是唯一的?
【发布时间】:2011-08-02 07:30:06
【问题描述】:

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

我有一些想法,但没有什么让我觉得这是解决这个问题的“最佳”解决方案。有没有我可以编写的方法来保证每次连续调用都会产生一个唯一的DateTime

我是否应该为此使用不同的类型,也许是 long int? DateTime 的明显优势是易于解释为实时,这与增量计数器不同。

更新:这是我最终编码为一个简单的折衷解决方案,它仍然允许我使用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;
    }
}

因为每个刻度值只有百万分之一秒,所以此方法仅在每秒调用 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 万个唯一时间戳,而最终结果只被提前了半秒。在实际应用中,这种偏差是不明显的。

【问题讨论】:

  • 应用程序是多线程的吗?这意味着两个不同的线程可以同时请求一个 ID?
  • 您不能使用Guids有什么原因吗?
  • 我正在使用 guid 来表示唯一身份。我在这里需要的是时间顺序......即能够查看两个更改并判断哪个先发生。向导不能这样做。理想情况下,我想使用某种形式的人类可解释的日期形式,但我认为递增的数字会在紧要关头起作用,也许与 DateTime 结合使用以提高可读性。
  • 调用new DateTime(_lastTime + 1, DateTimeKind.Utc);时指定DateTimeKind.Utc

标签: c# .net datetime concurrency


【解决方案1】:

获取严格升序且不重复的时间戳序列的一种方法是使用以下代码。

与此处的其他答案相比,此答案具有以下优点:

  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;
       }
   }
}

【讨论】:

  • +1。我对此代码进行了一些单元测试。它确实有效,并且执行速度比任何其他解决方案都要快。唯一的缺点是它对普通读者来说并不直观。我会花更多的时间向其他人解释它是如何工作的,而不是我更喜欢一个简单的锁(这非常快)。 [复杂性/性能权衡]。但是,如果可以的话,我会再次对您的解决方案投票。
  • 我发现它很容易理解,而且写得很好,除了变量名可能是英文单词。非常感谢!
  • 出色的解决方案
  • 请记住,系统时间可能会跳动一点,即当它从时间服务器获得更新时。因此,如果时钟向后跳,您将得到一个与上一个值不同的值,但是当时钟赶上时,您可能会再次捕获与之前相同的值,而您的算法将不知道这一点。
  • @MikeMarynowski 是的,但是对于静态的lastTimeStamp,它应该总是每次调用至少提前 1 次。因此,这仅适用于应用程序重新启动,并且仅当跳转大于重新启动应用程序的时间时。在跳回时,它将继续分发增量 +1 值,直到时钟赶上(而且您不太可能足够快地请求它们,以至于这不会很快回到接近实际时钟时间)。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    DateTime.Now 仅每 10-15 毫秒更新一次。

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

    How to get timestamp of tick precision in .NET / C#?

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

    【讨论】:

      【解决方案4】:

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

      如果您只是想要一个唯一的 id,它就像连接时间戳和计数器值并在它们之间使用分隔符一样简单。但是因为您希望这些值始终有序,所以这还不够。基本上,您需要做的就是使用原子计数器值为您的时间戳添加固定宽度精度。我是一名 Java 开发人员,所以我现在还不能提供 C# 示例代码,但是这两个领域的问题都是一样的。因此,只需按照以下一般步骤操作:

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

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

      【讨论】:

      • 这确实是最好的妥协。我正在考虑这样的事情作为可能的解决方案;感谢您详细介绍。我认为每 15 毫秒 100,000 次更改是一个可以接受的限制。 :)
      • 您也可以将值表示为字符串,而不是 64 位 int,然后简单地将任意长度的计数器连接到字符串的末尾(可能使用像 '.' 这样的分隔符) ,从而绕过每毫秒 6666 的“限制”。然后,为了便于阅读,您可以简单地将其从结尾处剪掉,以将初始部分再次解释为日期。
      • 是的,但是您必须担心订购问题。 "123456789.12345" &lt; "123456789.234" 如果您自然比较。两个部分都必须是固定宽度。虽然你当然可以增加第二部分的宽度。我只是指望时间的毫秒值在很长一段时间内不会以数字增加。在这一年里,2286 人会为你的所作所为而愤怒。
      • 对不起,我没有关注。您可以将字符串拆分为“。”字符并分别比较两个部分,或者用零填充第二部分。这与 2286 年有什么关系?
      • 抱歉,如果不清楚。拆分字符串并进行 2 次比较似乎很烦人,因为这样您就无法利用自然顺序。我只是说你想用零填充第二部分。然后关于 2286 年的评论是关于我建议这样做的方式。我要求时间戳中的位数不要改变,但在 2286 中它会。
      【解决方案5】:

      不能保证是唯一的,但也许使用ticks 足够细化?

      一个勾代表一百 纳秒或百万分之一 第二。有 10,000 个刻度 毫秒。

      【讨论】:

      • 不幸的是,它似乎是 DateTime.Now 大约每 15 毫秒更新一次,在这种情况下使 Ticks 属性毫无意义。
      • 有一个 API 技术涉及 GetTickCount() 来得到你想要的。
      【解决方案6】:

      不确定您要完全做什么,但可能会考虑使用队列来处理顺序处理记录。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-07
        • 2012-05-23
        • 2013-02-18
        • 1970-01-01
        • 1970-01-01
        • 2018-12-10
        • 2013-12-05
        相关资源
        最近更新 更多