Прежде чем начать, я хочу указать, что я уверен, что это действительно произошло. Все мои журналы показывают, что это произошло.
Я хотел бы знать, ошибаюсь ли я, и это невозможно, просто ли это невероятно маловероятно (что я подозреваю), или если это не так маловероятно, и я делаю что-то принципиально неправильное.
У меня есть 4 экземпляра того же кода, что и Windows Services на том же сервере. Этот сервер имеет многоядерный (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-разрядную строку для двух отдельных процессов.
Я посеял класс Random, используя DateTime.Now.Ticks, который, как я думал, обеспечил бы защиту между потоками - один тик - 100 наносекунд, наверняка два потока не могли бы получить одно и то же начальное значение.
Очевидно, что я не объяснил, что мы не говорим о потоках, мы говорим о процессах на многоядерном процессоре. Это означает, что этот код может буквально работать дважды одновременно. Я думаю, что это вызвало столкновение.
Два процесса, выполняющие один и тот же код с приблизительными 15-секундными интервалами, смогли получить один и тот же код за 100 наносекунд. Это возможно? Я здесь на правильном пути?
Буду благодарен за ваши мысли или предложения.
Чтобы уточнить, я не могу использовать GUID - внешний процесс, с которым я общаюсь, требует 18-значного числа. Он старый, и я не могу его изменить, к сожалению.
Для этой цели вам не нужны случайные номера, вам нужны уникальные номера. Я с @JP. Я думаю, вы должны посмотреть на GUID для своих идентификаторов сообщений.
EDIT: если вы не можете использовать GUID, подумайте о том, как получить 64-битное число, которое является уникальным, и использовать последовательные 3-битные куски его в качестве индекса в 8-символьный алфавит (подбрасывание неиспользуемых верхних бит). Один из способов сделать это - создать базу данных, в которой вы создадите запись для каждого нового сообщения с 64-разрядным целым числом с автоматическим увеличением в качестве ключа. Используйте ключ и переведите его в свой 18-значный идентификатор сообщения.
Если вы не хотите полагаться на базу данных, вы можете получить что-то, что работает при определенных условиях. Например, если сообщения должны быть уникальными в течение всего процесса, то вы можете использовать идентификатор процесса как 32 бита значения и получить оставшиеся 22 требуемых бита из генератора случайных чисел. Поскольку ни один из двух процессов, работающих в одно и то же время, не может иметь одинаковый идентификатор, у них должно быть гарантировано наличие уникальных идентификаторов сообщений.
Несомненно, есть много других способов сделать это, если ваша ситуация не вписывается в один из вышеперечисленных сценариев.
Если по каким-то причинам вы не можете этого сделать, вам следует использовать GUID для этой цели. Таким образом вы устраните столкновения.
За комментарий: вы можете использовать GUID и 64-бит хеш FNV и использовать XOR-folding, чтобы получить результат в пределах 59 бит, которые у вас есть. Не как доказательство столкновения как GUID, но лучше, чем у вас.
Другой способ добиться этого - не использовать класс Random, поскольку он чреват такими проблемами. Вы можете выполнить ту же функциональность (случайный 18-значный номер), используя генератор случайных чисел криптографического качества, доступный в System.Security.Cryptography.
Я изменил свой код, чтобы использовать класс RNGCryptoServiceProvider для генерации идентификатора.
// 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;
}
Попробуйте использовать эту функцию для семени вместо 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, вы увидите значения, квантующиеся примерно в 15 мс или около того. Таким образом, с 4 процессами на самом деле действительно вероятно, что два из них в конечном итоге будут использовать одно и то же семя для функции Random.
Так как функция GetSeed, основанная на Guid, обладает невероятной вероятностью создания дубликатов, в идеале вы хотели бы создать какой-то банк предварительно рассчитанных уникальных номеров. Однако, поскольку вы говорите об отдельных процессах здесь, вам придется придумать какой-то способ кэширования значений, где все процессы могли бы их читать, что беспокоит.
Если вы хотите волноваться о космически маловероятных событиях, покупайте лотерейные билеты.
Ип это может произойти, и поэтому он сделал.
Вы должны инициализировать Random только один раз, при запуске. Если у вас много потоков, начинающихся в одно и то же время, получите копию DateTime.Now.Ticks и передайте их каждому потоку с известным смещением, чтобы предотвратить его инициализацию в одно и то же время.
Я также согласен с идеей GUID.
Что касается вашей исходной проблемы, так как Ticks длинный, это утверждение:
(int)DateTime.Now.Ticks
приведет к переполнению. Не уверен, какая какая-то гадость случится тогда...