如何确保时间戳始终是唯一的
本文关键字:唯一 时间戳 何确保 确保 | 更新日期: 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万个唯一的时间戳,最终结果只提前了半秒。在实际应用程序中,这种倾斜是不明显的。
获得严格升序且没有重复的时间戳序列的一种方法是以下代码:
与这里的其他答案相比,这个答案有以下好处:
- 该值与实际实时值密切相关(除非在极端情况下,当请求率非常高时,它们会略微超过实时)。
- 它是无锁的,应该比使用
lock
语句的解决方案性能更好。 - 它保证升序(简单地附加一个循环计数器不能)。
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#示例代码,但问题是在这两个领域是相同的。因此,只需遵循以下一般步骤:
- 您将需要一个方法来为您提供从0-99999循环的计数器值。100000是将毫秒精度时间戳与64位长的固定宽度值连接时可能的最大值数。因此,您基本上假设在单个时间戳分辨率(15毫秒左右)内永远不需要超过100000个id。使用Interlocked类提供原子递增和重置为0的静态方法是理想的方法。
- 现在要生成id,只需将时间戳与计数器值连接到5个字符。因此,如果时间戳是13023991070123,计数器是234,那么id就是1302399107012300234。
只要您不需要超过6666/ms的id(假设15ms是您最精细的分辨率),该策略就可以工作,并且在重新启动应用程序时始终无需保存任何状态。
不能保证是唯一的,但是也许使用tick足够细粒度吗?
一个刻度代表100纳秒或千万分之一秒第二。a里有10000个蜱虫毫秒。
不确定你想做什么,但也许可以考虑使用队列来处理顺序处理记录。