GUID不是唯一的简单证明

323

我想证明GUID在简单测试程序中不是唯一的。我原以为下面的代码会运行几个小时,但它没有工作。我该怎么让它工作?

BigInteger begin = new BigInteger((long)0);
BigInteger end = new BigInteger("340282366920938463463374607431768211456",10);  //2^128
for(begin; begin<end; begin++)
  Console.WriteLine(System.Guid.NewGuid().ToString());

我正在使用C#。


107
作为一名软件开发者,如果用户向您反映“它不工作了”,您会怎么回答? - JoshJordan
152
等待数万亿年。 - hobbs
67
因为这是我今天在网上看到最有趣的事情,所以点了个赞。 - jrockway
32
@jrockway - 哈哈。我发现关于这个问题的任何信息都基本上是错误的,让我看得越久就越好笑。 - tylerl
243
它只是全局唯一的,因此它只在我们的星球上是唯一的。如果您想要一个真正唯一的ID,您需要使用全局唯一标识符(UUID)。我猜您只对我们的宇宙内的唯一性感兴趣。 :-) - tvanfosson
显示剩余24条评论
30个回答

407

Kai,我提供了一个使用线程完成你所需的程序。它的许可证条款如下:您必须按每个CPU核心每小时0.0001美元的价格向我支付费用。费用应在每个日历月底支付。请尽快联系我获取我的PayPal帐户详细信息。

using System;
using System.Collections.Generic;
using System.Linq;

namespace GuidCollisionDetector
{
    class Program
    {
        static void Main(string[] args)
        {
            //var reserveSomeRam = new byte[1024 * 1024 * 100];     // This indeed has no effect.

            Console.WriteLine("{0:u} - Building a bigHeapOGuids.", DateTime.Now);
            // Fill up memory with guids.
            var bigHeapOGuids = new HashSet<Guid>();
            try
            {
                do
                {
                    bigHeapOGuids.Add(Guid.NewGuid());
                } while (true);
            }
            catch (OutOfMemoryException)
            {
                // Release the ram we allocated up front.
                // Actually, these are pointless too.
                //GC.KeepAlive(reserveSomeRam);
                //GC.Collect();
            }
            Console.WriteLine("{0:u} - Built bigHeapOGuids, contains {1} of them.", DateTime.Now, bigHeapOGuids.LongCount());


            // Spool up some threads to keep checking if there's a match.
            // Keep running until the heat death of the universe.
            for (long k = 0; k < Int64.MaxValue; k++)
            {
                for (long j = 0; j < Int64.MaxValue; j++)
                {
                    Console.WriteLine("{0:u} - Looking for collisions with {1} thread(s)....", DateTime.Now, Environment.ProcessorCount);
                    System.Threading.Tasks.Parallel.For(0, Int32.MaxValue, (i) =>
                    {
                        if (bigHeapOGuids.Contains(Guid.NewGuid()))
                            throw new ApplicationException("Guids collided! Oh my gosh!");
                    }
                    );
                    Console.WriteLine("{0:u} - That was another {1} attempts without a collision.", DateTime.Now, ((long)Int32.MaxValue) * Environment.ProcessorCount);
                }
            }
            Console.WriteLine("Umm... why hasn't the universe ended yet?");
        }
    }
}

PS: 我想尝试使用Parallel扩展库。那很简单。

而将OutOfMemoryException用作控制流程感觉不对。

编辑

嗯,看来这仍然吸引了很多投票。所以我已经解决了GC.KeepAlive()问题,并改为在C# 4中运行。

为了澄清我的支持条款:支持仅在2010年2月28日提供。请使用时光机仅在当天进行支持请求。

编辑2 与我自己管理内存的所有以前尝试一样,GC在管理内存方面做得更好。


120
最后那个Console.WriteLine让我笑得很厉害。我认为你应该抛出一个CommonlyAcceptedCosmologicTheoriesWrongException而不是写输出语句。 - R. Martinho Fernandes
17
将此标记为已接受是否意味着@Kai也接受@ligos规定的条款? - kb.
4
@devinb请解释一下?看起来它正在释放先前分配的字节,以便GC可以Collect()它。为什么它没有实现任何东西? - mythz
3
GuidCollisionDetector。这个名字有潜力。 - Ufuk Hacıoğulları
3
谢谢你提供这段代码,我已将它添加到我的 GUID 生成器代码中,用于制作跨数据库外键。它帮了我们大忙,避免了之前一直遇到的冲突问题。但是,现在我的应用程序卡住了,我不知道为什么!可能是 .Net 框架的 bug 或者其他原因。 - WOPR
显示剩余17条评论

226

这个程序需要运行的时间会远远超过数小时。假设它以1 GHz循环(实际上它无法达到那么快),那么它要运行10790283070806014188970年,大约比宇宙的年龄长830亿倍。

如果假设摩尔定律成立,那么不运行这个程序,等待几百年后再在速度快了数十亿倍的电脑上运行它会更快些。事实上,任何运行时间长于CPU速度翻倍所需时间(大约18个月)的程序,如果你等CPU速度提高并购买新的CPU才运行它的话,将比直接运行它更快完成(除非你编写这个程序让它可以暂停并在新硬件上恢复运行)。


27
那么也许创建多个线程生成 GUID 是一个更好的主意? - Kai
107
在四核处理器上运行4个线程将使其在宇宙年龄的200亿倍速度下运行 - 所以,是的,这会大有帮助。 - rjmunro
34
我怀疑这是个恶意评论,但如果不是的话:线程并没有神奇的功效。如果单线程每秒可以执行十亿次操作,那么增加到十个线程意味着每个线程运行时间缩短为原来的1/10。每个线程每秒执行1亿次操作;每秒总操作数并没有增加。增加每秒操作数的方法是购买更多的计算机。假设你购买了十亿台计算机。那将把问题缩减至需要花费10790283070806年,仍然需要超过四小时。 - Eric Lippert
10
我认为rjmunro假设每个线程将在独立的核心上运行;830亿个宇宙/4个核心确实约等于200亿个宇宙。是时候购买英特尔股票了! - Dour High Arch
4
以你每秒1亿个GUID的速度,只需634年就有50%的几率发生冲突。 - Jason Goemaat
显示剩余12条评论

170

GUID(全局唯一标识符)理论上是非唯一的。这里有证据:

  • GUID是一个128位的数字
  • 你无法生成2^128+1或更多的GUID而不重复使用旧的GUID

然而,如果整个太阳的功率被用于执行此任务,它将在完成之前变冷。

可以使用多种不同的策略生成GUID,其中一些采取特殊措施来保证给定的机器不会生成相同的GUID两次。在特定算法中找到冲突将表明您生成GUID的特定方法很差,但不会证明任何关于GUID的总体情况。


44
鸽笼原理来拯救! - yfeldblum
22
对于太阳变冷的评论点赞。有一个有趣的评论提到,加密密钥大于256位是没有意义的。枚举所有可能的密钥值所需的能量比整个宇宙储存的能量还要多。在CPU中切换一个比特需要一定的能量(这就是产生热量的原因),当这个操作被乘以2^256次时,得到的是一个非常巨大的数字,超过了宇宙中储存的能量。使用E=mc2公式可以计算出宇宙需要2^227千克的质量,而我们的太阳质量为2^101千克,也就是说需要2^126个太阳的质量! - Skizz
31
仅适用于暴力攻击。当加密方案被“破解”时,意味着它可以在比暴力攻击更短的时间内被解决,但解密时间仍与密钥大小成比例。 - Steven Sudit
1
@StevenSudit:与密钥大小的指数成比例(除非P==NP)。 - Ihar Bury
1
圣母耶稣。别争了。http://xkcd.com/386/ - tylerl
显示剩余3条评论

137
当然,GUID可能会发生碰撞。由于GUID是128位的,只需生成鸽笼原理下的2^128 + 1个GUID,就必定会发生碰撞。
但是当我们说GUID是唯一的时,我们实际上是指密钥空间非常大,因此在随机生成GUID时,偶然生成相同的GUID是几乎不可能的。
如果您随机生成一个长度为n的GUID序列,则至少发生一次碰撞的概率约为p(n) = 1 - exp(-n^2 / 2 * 2^128)(这是生日问题,其中可能的生日数量为2^128)。
   n     p(n)
2^30 1.69e-21
2^40 1.77e-15
2^50 1.86e-10
2^60 1.95e-03

为了让这些数字更具体化,2^60 = 1.15e+18。因此,如果您每秒生成十亿个GUID,那么要生成2^60个随机GUID将需要36年的时间,即使如此,发生冲突的概率仍然是1.95e-03。在未来36年内,你比发现冲突更有可能某个时刻被谋杀4.76e-03)。祝好运。


239
如果你在一生中遭受谋杀,那么可能性最大的时间是在生命的尽头。 - Michael Myers
25
@mmyers说得很好。这意味着我现在被谋杀的几率极低,因为这不是我的生命终点。哦,等等... - Steven Sudit
@Joe:谁说你必须使用现成的生成算法? - jason
17
你假设被谋杀的概率对于所有人都是恒定的。但显然在论坛帖子中写讽刺话的人比普通人更容易被谋杀。 - Jay
@Joe:现在使用的标准算法是随机数,其中有一些位用于指示生成算法。因此,随机位略少于128位。你说得对,最初的算法使用了时间戳和MAC代码,但媒体上出现了关于“Microsoft在MS办公文档中的隐藏位置[GUID]中插入计算机身份[MAC地址]”的大骚动,所以他们改用了随机方案。 - user9876
显示剩余2条评论

61
如果您担心唯一性,您可以随时购买新的GUIDs,这样您就可以丢弃旧的GUID。如果您需要,我可以在eBay上发布一些GUID。

13
酷,完整套装的价格是多少,从0到(2^128)-1? - user180247
23
特价出售,每千个全局唯一标识符仅售0.01美元。如果您在接下来的60分钟内下单,我会顺带赠送一些竹制风铃。 - ctacke
7
我的套装更具独特性和高品质。它们经过了双重检查和验证,这使它们价值每个全局唯一标识符(GUID)的1美元。如果您不想一次性进行全部投资,甚至可以分批购买它们。但是,请注意,每批需要额外收费10美元。 - Thomas
3
我会为您设置一个月度方案,给您提供无限的指导服务,价格公道。^那些人试图欺骗您,出售高价的指导服务。我将向您销售中国制造的优质指导服务! - ErocM

47

就我个人而言,我认为“宇宙大爆炸”是由两个全局唯一标识符(GUID)碰撞造成的。


4
请记住,需要一种“特殊”的程序员才能做到这一点... - AnthonyLambert
我想听听你对自己理论的推理。我认为我们可以基于此创立一个新的宗教,并招募汤姆·克鲁斯! - ErocM
@ErocM;参阅“膜宇宙学”(http://en.wikipedia.org/wiki/Brane_cosmology)和“膜(M-Theory)”(http://en.wikipedia.org/wiki/Membrane_(M-Theory))。这个想法是,如果两个膜接触,就会创建一个新的宇宙。因此,你可以推断出,如果两个GUID接触,也会创建一个新的宇宙。 - AMissico
2
如果时间警察教给我们什么,那就是同一物质不能在任何给定的时间占据相同的空间。因此,如果两个GUID发生碰撞,它们将互相消耗,导致的内爆会产生一个黑洞,吞噬整个宇宙。所以实际上,它不会创造一个宇宙,而是摧毁它。 - AJC

42

你可以使用量子bogosort算法的变种,在O(1)时间内展示它。

Guid g1 = Guid.NewGuid();
Guid g2 = Guid.NewGuid();
if(g1 != g2) Universe.Current.Destroy();

21
调用Destroy()时,我遇到了一个异常。根据文字描述,我认为我的计算机缺少摧毁当前宇宙所需的硬件。你知道我可以在哪里获取这个硬件吗? - Steven Sudit
11
@Steven: 不,一些管理人员担心那个API会对公众造成太多负面影响,因此命令它始终失败以达到“安全原因”的目的。如果你查看这个方法的源代码,就只有一行:throw new MundaneHardwareException();。不管怎样,我听说CERN的那些家伙有一种叫做“大强子玩意儿”的东西,可能能解决问题... - R. Martinho Fernandes
7
@Martinho: 啊,好的。我会考虑用Cern.Lhc.DestroyThisUniverse()替换Universe.Current.Destroy() - Steven Sudit
61
我知道我使用 Haskell 编程的原因了。这些副作用变得令人害怕了。 - Edward Kmett
6
有一种理论认为,如果有人确切地发现了宇宙存在的意义以及它为什么存在,那么宇宙将会立即消失并被更加奇怪难解的事物所取代。还有一种理论认为,这已经发生了。——《银河系漫游指南》Douglas Adams - Mike Pirnat
显示剩余5条评论

28
任意两个GUID很可能是唯一的(不相等)。
参见此SO条目,以及来自维基百科的信息:

虽然每个生成的GUID不能保证是唯一的,但唯一键的总数(2^128或3.4×10^38)非常大,因此生成两次相同的数字的概率非常小。例如,考虑可观测的宇宙,它包含约5×10^22颗星星;那么每颗星星都可以拥有6.8×10^15个独特的全局唯一标识符。

所以,你可能需要等待更多亿万年,并希望在我们所知道的宇宙结束之前找到一个。

那么2的128次方不是GUID可能的正确数量吗? - Kai
21
这没错。你认为2^128是一个小数吗? - jrockway
是的,2^128是可能的GUID数量。 - Graviton
3
这是一个非常大的数字。 $ irb >> 2 ** 128 => 340282366920938463463374607431768211456 - adamJLev
45
即使对于你来说呢? - Austin Richardson

27

[更新:] 正如下面的评论所指出的那样,较新的 MS GUID 是 V4,不使用 MAC 地址作为 GUID 生成的一部分(我还没有看到来自微软的 V5 实现的任何迹象,如果有人有确认链接,请告诉我)。但是,在 V4 中,时间仍然是一个因素,GUID 重复的几率仍然非常小,对于任何实际用途而言都是不相关的。您肯定不太可能从仅进行单个系统测试(例如 OP 尝试的测试)中生成重复的 GUID。

这些答案中大多数都遗漏了关于 Microsoft GUID 实现的一个关键点。GUID 的第一部分基于时间戳,另一部分基于网络卡的 MAC 地址(如果未安装 NIC,则是随机数字)。

如果我理解正确,这意味着重复 GUID 的唯一可靠方法是在具有相同 MAC 地址且两个系统的时钟在生成发生时完全相同时,在多个计算机上同时运行 GUID 生成(如果我理解正确,时间戳是基于毫秒计算)......即使那时仍有许多其他位数是随机的,因此概率仍然极小。

就所有实际目的而言,GUIDs 都是独一无二的。

“The Old New Thing”博客上有一个相当好的 MS GUID 描述。


3
使用虚拟化技术,实际上这是可行的。您可能会得到重复的GUID(全局唯一标识符)。 - Goran
8
尽管如此,Raymond在MAC地址方面已经过时了,微软不再使用这些内容。请参阅http://en.wikipedia.org/wiki/GUID#Algorithm以了解V1和V4 Guid之间的区别。 - Michael Stum
1
这已经不再是事实了。当前的V5方案只是128位纯伪随机好处。 - Edward Kmett
有趣的是,你把我一个月前做的事情都说出来了,而你得到了16分,而我仍然没有得到任何分数? - AnthonyLambert
1
亚Tony,这有点奇怪。当我回答这篇帖子时,只有3或4个答案,我不记得看到你的...如果我看到了,我会给你点赞的。通常情况下,如果已经有其他足够好的答案覆盖了问题,我就不会回答问题(这也是为什么我的总声望可能相对较低的原因)。 - Stephen M. Redd

23

如果您想在代码的许多地方检查 GUID 的唯一性,这里有一个非常巧妙的扩展方法。

internal static class GuidExt
{
    public static bool IsUnique(this Guid guid)
    {
        while (guid != Guid.NewGuid())
        { }
        return false;
    }
}

只需在生成新的guid时调用Guid.IsUnique即可...

Guid g = Guid.NewGuid();
if (!g.IsUnique())
{
    throw new GuidIsNotUniqueException();
}

...检查,我甚至建议调用两次以确保第一轮获取正确。


2
这怎么确保 this guid 没有在世界上其他地方生成过呢?:p 哎呀,我们需要一个全球唯一标识池。 :) - nawfal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接