使用 DateTime.Now.Ticks 生成唯一的数字 ID
Posted
技术标签:
【中文标题】使用 DateTime.Now.Ticks 生成唯一的数字 ID【英文标题】:Generating Unique Numeric IDs using DateTime.Now.Ticks 【发布时间】:2011-11-26 11:30:16 【问题描述】:我需要生成一个唯一的数字 ID 以附加到传入请求。此 ID 仅用于临时跟踪请求,并且在请求完成处理后将被丢弃。此 ID 将仅在此应用程序的上下文中使用,但需要以高性能多线程方式分配。
我正在考虑将 DateTime.Now.Ticks 用于此 ID,但想知道如果同时处理同时请求,DateTime.Now.Ticks 是否仍然可以生成冲突 ID?
如果有人能提出一种更好的方法来在多线程环境中生成这些 ID(最好是不是像 Tick 那样的 Int64 的),请告诉我。像递增数字这样简单的东西就足够了,只要我不必在递增之前锁定数字。
非常感谢您的帮助。
【问题讨论】:
使用 GUID/UUID 有什么问题? 生成 GUID 的开销太大。我需要的 ID 对于进程来说是唯一的(不是全局的),并且只在每个请求的持续时间内使用。 “开销太大”? GUID 只需要生成几个字节的随机数据。它并不昂贵。听起来您正在进行过早的优化。 也许它在实际时间中是微不足道的,但从一些基本测试来看,它看起来比具有 8 个并发线程的 interlocked.increment 慢 10 倍左右。 那么公平,如果您生成的 ID 足够重要。 【参考方案1】:您只需要使用一个静态变量,每次您想要另一个唯一值时该变量都会递增。您可以使用Interlocked.Increment 方法使该线程安全且仍然非常快...
// Declaration
private static int safeInstanceCount = 0;
// Usage
...
Interlocked.Increment(ref safeInstanceCount);
...
【讨论】:
我已经在考虑 interlock.increment 但我希望避免任何类型的锁定。但是,正如您提到的那样,性能影响可能可以忽略不计,所以我可能会继续这样做,而不是花费太多过度优化。谢谢。 除非您计划每分钟调用数百万次,否则性能影响不值得考虑。此外,它非常易于使用。 @PhilWright:在我的 linux 机器上,time csharp <<< "int x; while (System.Threading.Interlocked.Increment(ref x)<1000000000);"
显示它每秒执行大约 121,521,448 次增量(比 x++
慢 2.6 倍。所以(一如既往)它实际上取决于 what否则无论您需要优化什么,都需要适应那一秒。
但是,线程处理时性能会下降:ThreadStart a = () => int x=0; while(Interlocked.Increment(ref x) < 1000000000); ;
并行 x 4 需要 9s;共享 int x
(通过将其从 lambda 中取出)需要 32 秒(仅 ~31 miljon incr/s)——而对 Interlocked.Increment 的总体调用次数实际上是小到 4 倍:)
有趣的反馈。我怀疑 interlock.increment 所花费的时间与提供的实际操作时间相比很小。【参考方案2】:
DateTime.Now
对于这个目的来说绝对是可怕的。充其量,您将获得 1 毫秒的分辨率;在最坏的情况下,NT 为 17 毫秒,CE/Compact Framework 为 1 秒 (!)。
考虑将Interlocked.Increment
方法用于快速、线程安全的计数器。
【讨论】:
This article 说:“一毫秒有 10,000 个滴答声。” @PeterX 1 tick = 100ns 的定义绝不意味着DateTime.Now
的实现能够达到这样的精度;我遇到了我在帖子中提到的所有实现限制。如果你真的需要微秒级的精度,Stopwatch
会更好。
不用担心 - 是的,我已移至 Guid
只是为了确保我的临时文件夹 - 无论如何都已清理 - 是唯一的。【参考方案3】:
从每个线程 ID(如果多个线程发起请求)开始,与每个线程计数器连接(如果每个线程预计会发起多个请求)。
【讨论】:
【参考方案4】:只需获取强随机数或使用 GUID
http://www.codeproject.com/KB/security/CryptoPasswordGenerator.aspxhttp://msdn.microsoft.com/en-us/library/system.random.aspx
http://msdn.microsoft.com/en-us/library/system.guid.newguid.aspx
如果高性能是必须具备的特性,请按单调顺序分配序号。通过为每个处理消息的线程“保留”一系列(例如,20-100)ID 来防止锁争用。这样,您只需在 20-100 次迭代中锁定序列生成器一次。
【讨论】:
使用随机似乎你可能会“随机”发生碰撞。 性能是必须的,这就是我回避 guid 或不得不计算 ID 的原因。为每个线程保留一系列 ID 是一个好主意。我会仔细考虑一下。谢谢。 @KirkWoll:如果随机足够随机,则不会。由于这个原因,可以假定 GUID 不会发生冲突,这一点已被广泛接受。【参考方案5】:如果您知道您将拥有多少线程(或至少一个上限),您可以在线程之间划分您的 ID 空间,将 ID 计算为(线程本地)计数器的值和线程的 ID - 例如,counter_value++ << 8 | thread_id
。因此,线程之间不需要协调或锁定,生成 ID 只需要增量、位移和或。
如果您为此使用系统线程 ID,您的 ID 会稍长一些,但您无需手动为线程分配 ID。
【讨论】:
【参考方案6】:可以使用这个属性,但需要支付1ms,这并不重要!
public static long UniqId
get
Thread.Sleep(1);
return long.Parse(DateTime.Now.ToString("yyMMddHHmmssffff"));
【讨论】:
【参考方案7】:我正在使用基于时间的简单生成 ID,它可能会有所帮助。
private static readonly string prefixNumber = ConfigManager.Current.GetAppSetting("AppTimeIdPrefix", "");
private static readonly DateTime epoch = DateTime.SpecifyKind(DateTime.Parse(ConfigManager.Current.GetAppSetting("AppTimeIdEpoch", "1970/01/01")), DateTimeKind.Utc);
/// <summary>
/// DateTime.Now is only updated every 10-15ms.
/// </summary>
private static long lastTime = CurrentTimeMilliseconds();
private static readonly object timeLock = new object();
private static long CurrentTimeMilliseconds()
return (long)(DateTime.UtcNow.ToUniversalTime() - epoch).TotalMilliseconds;
public static long CreateId()
lock (timeLock)
// prevent concurrent access to ensure uniqueness
long original, newValue;
do
original = lastTime;
newValue = Math.Max(CurrentTimeMilliseconds(), original + 1);
while (Interlocked.CompareExchange(ref lastTime, newValue, original) != original);
return long.Parse(string.Format("01", prefixNumber, newValue));
参考How to ensure a timestamp is always unique?
【讨论】:
在锁保护区域内使用Interlocked
类首先违背了使用Interlocked
的目的(避免锁引起的争用)。以上是关于使用 DateTime.Now.Ticks 生成唯一的数字 ID的主要内容,如果未能解决你的问题,请参考以下文章
我可以获得与 DateTime.Now.Ticks 等效的 VBA 吗?
C# 怎样把 DateTime.Now.Ticks转换为常规日期的形式
是否可以减少 DateTime.Now.Ticks.ToString("X") 的长度并仍然保持唯一性?