Случайные числа сталкиваются с одним и тем же кодом .Net в разных процессах

2

Прежде чем начать, я хочу указать, что я уверен, что это действительно произошло. Все мои журналы показывают, что это произошло.

Я хотел бы знать, ошибаюсь ли я, и это невозможно, просто ли это невероятно маловероятно (что я подозреваю), или если это не так маловероятно, и я делаю что-то принципиально неправильное.

У меня есть 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-значного числа. Он старый, и я не могу его изменить, к сожалению.

Теги:
multithreading
random
multicore

6 ответов

5
Лучший ответ

Для этой цели вам не нужны случайные номера, вам нужны уникальные номера. Я с @JP. Я думаю, вы должны посмотреть на GUID для своих идентификаторов сообщений.

EDIT: если вы не можете использовать GUID, подумайте о том, как получить 64-битное число, которое является уникальным, и использовать последовательные 3-битные куски его в качестве индекса в 8-символьный алфавит (подбрасывание неиспользуемых верхних бит). Один из способов сделать это - создать базу данных, в которой вы создадите запись для каждого нового сообщения с 64-разрядным целым числом с автоматическим увеличением в качестве ключа. Используйте ключ и переведите его в свой 18-значный идентификатор сообщения.

Если вы не хотите полагаться на базу данных, вы можете получить что-то, что работает при определенных условиях. Например, если сообщения должны быть уникальными в течение всего процесса, то вы можете использовать идентификатор процесса как 32 бита значения и получить оставшиеся 22 требуемых бита из генератора случайных чисел. Поскольку ни один из двух процессов, работающих в одно и то же время, не может иметь одинаковый идентификатор, у них должно быть гарантировано наличие уникальных идентификаторов сообщений.

Несомненно, есть много других способов сделать это, если ваша ситуация не вписывается в один из вышеперечисленных сценариев.

  • 0
    Я согласен. Вы должны использовать GUID.
  • 0
    См. Комментарий к ответу JP - невозможно использовать GUID.
Показать ещё 2 комментария
7

Если по каким-то причинам вы не можете этого сделать, вам следует использовать GUID для этой цели. Таким образом вы устраните столкновения.

За комментарий: вы можете использовать GUID и 64-бит хеш FNV и использовать XOR-folding, чтобы получить результат в пределах 59 бит, которые у вас есть. Не как доказательство столкновения как GUID, но лучше, чем у вас.

  • 0
    Сложность в том, что устаревшему процессу, о котором я говорю, нужен 18-значный номер. Я бы использовал GUID, но я не могу.
  • 0
    Спасибо за обновления. Я думаю, что мораль этой истории не в том, чтобы использовать случайное, когда вы имеете в виду уникальное ...
Показать ещё 1 комментарий
3

Другой способ добиться этого - не использовать класс 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;
}
  • 0
    Я не слышал об этом классе раньше - спасибо, это определенно может быть полезно!
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, вы увидите значения, квантующиеся примерно в 15 мс или около того. Таким образом, с 4 процессами на самом деле действительно вероятно, что два из них в конечном итоге будут использовать одно и то же семя для функции Random.

Так как функция GetSeed, основанная на Guid, обладает невероятной вероятностью создания дубликатов, в идеале вы хотели бы создать какой-то банк предварительно рассчитанных уникальных номеров. Однако, поскольку вы говорите об отдельных процессах здесь, вам придется придумать какой-то способ кэширования значений, где все процессы могли бы их читать, что беспокоит.

Если вы хотите волноваться о космически маловероятных событиях, покупайте лотерейные билеты.

  • 0
    Интересно, что мои исследования (после события) показали, что разрешение DateTime.Now на практике составляет около 15 мс ... Заставляет задуматься, почему они предлагают тиков ...
  • 0
    Почему Visual Basic использовал твики вместо пикселей? Потому что Билл Гейтс ненавидит нас лично.
1

Ип это может произойти, и поэтому он сделал.

Вы должны инициализировать Random только один раз, при запуске. Если у вас много потоков, начинающихся в одно и то же время, получите копию DateTime.Now.Ticks и передайте их каждому потоку с известным смещением, чтобы предотвратить его инициализацию в одно и то же время.

  • 0
    Они не темы, хотя. Каждый из них работает в совершенно отдельном процессе.
0

Я также согласен с идеей GUID.

Что касается вашей исходной проблемы, так как Ticks длинный, это утверждение:

(int)DateTime.Now.Ticks

приведет к переполнению. Не уверен, какая какая-то гадость случится тогда...

  • 0
    Похоже, что бросает долго, чтобы int просто оборачивается. Это не исключение, поэтому все будет в порядке как начальное число.
  • 0
    Не исключение Но обтекание увеличивает вероятность столкновения, особенно если таймер квантуется.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню