在开始之前,我想指出我很确定这实际发生了。我的所有日志都表明它确实存在。
我想知道我是否错了,这是不可能的,是否只是非常不可能(我怀疑),或者它是不是那么不可能,而且我正在做一些根本错误的事情。
我在同一台服务器上运行了与Windows服务相同的4个相同代码实例。该服务器有一个多核(4)处理器。
以下是代码摘要:
public class MyProcess
{
private System.Timers.Timer timer;
// execution starts here
public void EntryPoint()
{
timer = new System.Timers.Timer(15000); // 15 seconds
timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
timer.AutoReset = false;
Timer_Elapsed(this, null);
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string uid = GetUID();
// this bit of code sends a message to an external process.
// It uses the uid as an identifier - these shouldn't clash!
CommunicationClass.SendMessage(uid);
timer.Start();
}
// returns an 18 digit number as a string
private string GetUID()
{
string rndString = "";
Random rnd = new Random((int)DateTime.Now.Ticks);
for (int i = 0; i < 18; i++)
{
rndString += rnd.Next(0, 10);
}
return rndString;
}
接收这些消息的外部进程感到困惑 - 我认为因为同一个uid来自两个独立的进程。基于此,似乎GetUID()
方法为两个单独的进程返回了相同的“随机”18位数字符串。
我使用DateTime.Now.Ticks播种Random类,我认为它会在线程之间提供保护 - 一个tick是100纳秒,当然两个线程无法获得相同的种子值。
我没有说明的是我们不是在谈论线程,我们在讨论多核处理器上的进程。这意味着此代码可以字面同时运行两次。我认为这就是造成冲突的原因。
以大约15秒的间隔运行相同代码的两个进程设法在100纳秒内命中相同的代码。这可能吗?我在这里走在正确的轨道上吗?
我很感激你的想法或建议。
为了澄清,我真的不能使用GUID - 我正在与之通信的外部进程需要一个18位数字。它已经老了,不幸的是我无法改变它。
答案 0 :(得分:7)
除非有某些原因你不能,否则你应该考虑使用GUID来达到这个目的。你将以这种方式消除碰撞。
每条评论:您可以使用GUID和64位FNV hash并使用XOR-folding将结果调整到您拥有的59位内。不像GUID那样具有防撞功能,但比你拥有的要好。
答案 1 :(得分:5)
您不希望随机数字用于此目的,您需要唯一数字。我和@JP在一起。我认为你应该考虑使用GUID作为你的消息ID。
编辑:如果您不能使用GUID,那么想办法获得一个唯一的64位数字,并使用它的连续3位数据块作为索引8个字符的字母表(抛弃未使用的高位)。一种方法是使用一个数据库,在其中为每个新消息创建一个条目,并使用自动递增的64位整数作为键。使用密钥并将其翻译成18个字符的消息ID。
如果您不想依赖数据库,您可以获得在某些条件下有效的内容。例如,如果消息在进程的生命周期中只需要是唯一的,那么您可以将进程ID用作值的32位,并从随机数生成器获取剩余的22个必需位。由于没有两个进程同时运行可以具有相同的id,因此应保证它们具有唯一的消息ID。
如果您的情况不符合上述情况之一,毫无疑问还有很多其他方法可以做到这一点。
答案 2 :(得分:3)
尝试将此函数用于种子,代替DateTime.Now.Ticks:
public static int GetSeed()
{
byte[] raw = Guid.NewGuid().ToByteArray();
int i1 = BitConverter.ToInt32(raw, 0);
int i2 = BitConverter.ToInt32(raw, 4);
int i3 = BitConverter.ToInt32(raw, 8);
int i4 = BitConverter.ToInt32(raw, 12);
long val = i1 + i2 + i3 + i4;
while (val > int.MaxValue)
{
val -= int.MaxValue;
}
return (int)val;
}
这基本上将Guid变成了一个int。理论上你可以得到重复,但它在宇宙上不太可能。
编辑:甚至只使用:
Guid.NewGuid().GetHashCode();
另一方面,使用DateTime.Now.Ticks几乎可以保证在某些时候发生碰撞。在Windows编程中,以远远超出计时器实际精度的单位指定计时器的分辨率是很常见的(我首先使用Visual Basic 3.0的计时器控件进行了此操作,该计时器控制设置为毫秒,但实际上只有18次第二)。我肯定不知道这一点,但我敢打赌,如果你只是运行一个循环并打印出DateTime.Now.Ticks,你会看到量化大约15ms间隔左右的值。因此,随着4个进程的进行,实际上其中两个最终可能会使用完全相同的随机函数种子。
由于基于Guid的GetSeed函数生成重复项的可能性非常小,因此理想情况下,您需要创建某种预先计算的唯一数字库。但是,由于您在这里讨论的是单独的进程,因此您必须想出一些缓存所有进程可以读取它们的值的方法,这很麻烦。
如果您想担心不太可能发生的事件,请购买彩票。
答案 3 :(得分:3)
实现此目的的另一种方法是不使用Random类,因为它充满了这样的问题。您可以使用System.Security.Cryptography中提供的加密质量随机数生成器来完成相同的功能(随机的18位数字)。
我已修改您的代码以使用RNGCryptoServiceProvider类生成ID。
// returns an 18 digit number as a string
private string GetUID()
{
string rndString = "";
var rnd = new RNGCryptoServiceProvider();
var data = new byte[18];
rnd.GetBytes(data);
foreach(byte item in data)
{
rndString += Convert.ToString((int)item % 10);
}
return rndString;
}
答案 4 :(得分:1)
是的,它可以发生,因此它确实发生了。
您应该在启动时仅初始化一次Random。如果有多个线程同时启动,请获取DateTime.Now.Ticks的副本并将其传递给具有已知偏移量的每个线程,以防止在同一时间进行初始化。
答案 5 :(得分:0)
我也赞同GUID的想法。
至于你原来的问题,因为Ticks很长,这句话:
(int)DateTime.Now.Ticks
会导致溢出。不确定会发生什么样的肮脏......