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

时间:2011-04-10 00:44:06

标签: c# .net datetime concurrency

我正在使用时间戳来临时命令我的程序中的并发更改,并要求更改的每个时间戳都是唯一的。但是,我发现简单地调用DateTime.Now是不够的,因为如果快速连续调用,它通常会返回相同的值。

我有一些想法,但没有任何事情让我觉得这是“最好的”解决方案。有没有一种我可以编写的方法可以保证每次连续调用产生一个唯一的DateTime

我是否应该为此使用不同的类型,也许是长整数? DateTime具有明显的优势,即可以像实时一样轻松解释,而不像增量计数器。

更新:以下是我最终编写的一个简单的折衷解决方案,它仍然允许我使用DateTime作为我的临时密钥,同时在每次调用方法时确保唯一性:< / p>

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万个唯一时间戳,最终结果只推迟了半秒。在实际应用中,偏差是不明显的。

6 个答案:

答案 0 :(得分:68)

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

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

  1. 这些值与实际实时值密切相关(除非在极端情况下具有非常高的请求率,否则它们会稍微超前于实时)。
  2. 它是无锁的,应该使用lock语句更好地执行解决方案。
  3. 它保证升序(简单地附加循环计数器不会)。
  4. 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 :(得分:7)

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

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

答案 2 :(得分:6)

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

本身并不是一种欺骗,但这个主题有一些关于减少重复/提供更好的时序分辨率的想法:

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

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

答案 3 :(得分:2)

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

如果您只是寻找一个唯一的ID,那么就像将时间戳和计数器值与之间的分隔符连接一样简单。但是因为你希望值总是按顺序排列,这是不够的。基本上您需要做的就是使用原子计数器值为您的时间戳添加额外的固定宽度精度。我是一名Java开发人员,所以我还不能提供C#示例代码,但两个领域的问题都是相同的。所以请按照以下一般步骤进行操作:

  1. 您需要一种方法为您提供从0-99999开始循环的计数器值。 100000是将毫秒精度时间戳与64位长的固定宽度值连接时可能的最大值。因此,您基本上假设在单个时间戳分辨率(15ms左右)内永远不会需要超过100000个ID。一个静态方法,使用Interlocked类提供原子递增并重置为0是理想的方法。
  2. 现在要生成您的ID,您只需将时间戳与填充为5个字符的计数器值连接起来。因此,如果您的时间戳为13023991070123且您的计数器为234,则ID为1302399107012300234。
  3. 只要您不需要比每秒6666更快的ID(假设15ms是您最精细的分辨率),此策略将起作用,并且无需在重新启动应用程序时保存任何状态即可正常工作。

答案 4 :(得分:1)

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

  

单个刻度表示一百   纳秒或千万分之一   第二。一个蜱中有10,000个蜱虫   毫秒。

答案 5 :(得分:0)

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