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

C# 随机数在不同进程中与相同的.Net代码冲突,c#,.net,multithreading,random,multicore,C#,.net,Multithreading,Random,Multicore,在我开始之前,我想指出,我非常确定这确实发生了。我所有的日志都表明是这样的 我想知道我是否错了,这是不可能的,是否只是难以置信的不可能(我怀疑),或者是否不是那么不可能,我在做一些根本错误的事情。 我在同一台服务器上有4个与Windows服务运行相同代码的实例。此服务器有一个多核(4)处理器 以下是代码摘要: public class MyProcess { private System.Timers.Timer timer; // execution starts here

在我开始之前,我想指出,我非常确定这确实发生了。我所有的日志都表明是这样的

我想知道我是否错了,这是不可能的,是否只是难以置信的不可能(我怀疑),或者是否不是那么不可能,我在做一些根本错误的事情。

我在同一台服务器上有4个与Windows服务运行相同代码的实例。此服务器有一个多核(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;
    }
公共类MyProcess
{
专用系统计时器;
//执行从这里开始
公共void入口点()
{
定时器=新系统。定时器。定时器(15000);//15秒
timer.appeated+=新系统.Timers.ElapsedEventHandler(timer_appeated);
timer.AutoReset=false;
计时器_已过(此为空);
}
私有无效计时器(对象发送器,System.Timers.ElapsedEventArgs e)
{
字符串uid=GetUID();
//这段代码向外部进程发送消息。
//它使用uid作为标识符-它们不应该冲突!
通信类。发送消息(uid);
timer.Start();
}
//以字符串形式返回18位数字
私有字符串GetUID()
{
字符串rndString=“”;
Random rnd=新随机((int)DateTime.Now.Ticks);
对于(int i=0;i<18;i++)
{
rndString+=rnd.Next(0,10);
}
返回rndString;
}
接收这些消息的外部进程被搞糊涂了——我想是因为相同的uid来自两个单独的进程。基于此,
GetUID()
方法似乎为两个单独的进程返回了相同的“随机”18位字符串

我已经使用DateTime.Now.Ticks对随机类进行了种子设定,我认为它可以在线程之间提供保护-一个tick是100纳秒,当然两个线程不能获得相同的种子值

显然,我没有解释的是,我们谈论的不是线程,而是多核处理器上的进程。这意味着这段代码可以同时运行两次。我认为这就是造成冲突的原因

以大约15秒的间隔运行相同代码的两个进程设法在100纳秒内命中相同的代码。这可能吗?我在这方面做得对吗?

如果你有什么想法或建议,我将不胜感激



澄清一下,我不能真正使用GUID-我正在与之通信的外部进程需要一个18位数字。它很旧,很遗憾,我无法更改它。

除非出于某种原因您不能更改,否则您应该考虑使用GUID。这样可以消除冲突


根据注释:您可以使用GUID和64位,并使用将结果限制在现有的59位范围内。不像GUID那样防冲突,但比现有的更好。

Yep这是可能的,因此确实如此


您应该在启动时只初始化一次Random。如果有多个线程同时启动,请获取DateTime.Now.Ticks的副本,并将其以已知偏移量传递给每个线程,以防止在同一时间初始化。

您不需要Random数字,而需要unique数字。我是@JP的。我认为你应该考虑使用guid作为你的消息ID

编辑:如果无法使用GUID,请考虑一种方法,获取一个唯一的64位数字,并使用其中连续的3位块作为8字符字母表的索引(丢弃未使用的高位)。一种方法是使用一个数据库,在该数据库中,您可以使用一个自动递增的64位整数作为键为每条新邮件创建一个条目。使用该键并将其转换为18个字符的邮件id

如果您不想依赖数据库,您可以获得在特定条件下工作的消息。例如,如果消息只需要在进程生命周期内是唯一的,那么您可以使用进程id作为值的32位,并从随机数生成器中获取所需的其余22位。因为没有两个进程在运行同时可以有相同的id,应该保证它们有唯一的消息id


如果您的情况不符合上述任何一种情况,无疑还有许多其他方法可以做到这一点。

尝试将此函数用于种子,而不是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,但这不太可能

编辑:甚至只是使用:

Guid.NewGuid().GetHashCode();
另一方面,使用DateTime.Now.Ticks几乎可以保证在某个点发生冲突。在Windows编程中,以远远超出计时器实际精度的单位指定计时器分辨率是非常常见的(我第一次遇到这种情况时使用了Visual Basic 3.0的计时器控件,该控件以毫秒为单位设置,但实际上每秒只关闭18次).我不确定这一点,但我敢打赌,如果你只是运行一个循环并打印出DateTime.Now.Ticks,你会看到值以15毫秒左右的间隔量化。因此,随着4个进程的进行,实际上很可能其中两个进程最终会对随机函数使用完全相同的种子

由于基于Guid的GetSeed函数产生重复的可能性极小,理想情况下,您可能希望创建某种预先计算的唯一数字库。但是,由于这里讨论的是单独的进程,因此您必须想出一些方法来缓存所有进程都可以读取的值,这很麻烦


如果你想担心大规模不太可能发生的事件,买彩票。

另一种方法是不要使用随机类,因为它充满了这样的问题。哟
// 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;
}
(int)DateTime.Now.Ticks