随机数在不同进程中与相同的.Net代码冲突

时间:2009-07-16 00:00:18

标签: c# .net multithreading random multicore

在开始之前,我想指出我很确定这实际发生了。我的所有日​​志都表明它确实存在。

我想知道我是否错了,这是不可能的,是否只是非常不可能(我怀疑),或者它是不是那么不可能,而且我正在做一些根本错误的事情。

我在同一台服务器上运行了与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位数字。它已经老了,不幸的是我无法改变它。

6 个答案:

答案 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

会导致溢出。不确定会发生什么样的肮脏......