C# 随机数在不同进程中与相同的.Net代码冲突
在我开始之前,我想指出,我非常确定这确实发生了。我所有的日志都表明是这样的 我想知道我是否错了,这是不可能的,是否只是难以置信的不可能(我怀疑),或者是否不是那么不可能,我在做一些根本错误的事情。 我在同一台服务器上有4个与Windows服务运行相同代码的实例。此服务器有一个多核(4)处理器 以下是代码摘要: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
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