Является ли поток генератора случайных чисел С# безопасным?

Является ли метод С# Random.Next() потокобезопасным?

Ответ 1

В методе Next ничего особенного не сделано для обеспечения безопасности потоков. Однако это метод экземпляра. Если вы не разделяете экземпляры Random в разных потоках, вам не нужно беспокоиться о повреждении состояния в экземпляре. Не используйте один экземпляр Random в разных потоках без какой-либо исключительной блокировки.

У Джона Скита есть несколько хороших сообщений на эту тему:

StaticRandom
Пересматривая случайность

Как отмечают некоторые комментаторы, существует еще одна потенциальная проблема в использовании различных экземпляров Random которые не зависят от потока, но засеваются одинаково и, следовательно, вызывают идентичные последовательности псевдослучайных чисел, поскольку они могут быть созданы одновременно или в пределах близкого расстояния. временная близость друг к другу. Одним из способов решения этой проблемы является использование основного экземпляра Random (который заблокирован одним потоком) для генерации некоторых случайных начальных чисел и инициализации новых Random экземпляров для использования каждым другим потоком.

Ответ 2

Нет, использование одного и того же экземпляра из нескольких потоков может привести к его поломке и возврату всех нулей. Однако создать поточно-ориентированную версию (не требуя неприятных блокировок при каждом вызове Next()) просто. Адаптировано из идеи в этой статье:

public class ThreadSafeRandom
{
    private static readonly Random _global = new Random();
    [ThreadStatic] private static Random _local;

    public ThreadSafeRandom()
    {
        if (_local == null)
        {
            lock (_global)
            {
                if (_local == null)
                {
                    int seed = _global.Next();
                    _local = new Random(seed);
                }
            }
        }
    }

    public int Next()
    {
        return _local.Next();
    }
}

Идея состоит в том, чтобы сохранить отдельную static Random переменную static Random для каждого потока. Однако сделать это очевидным способом не удается из-за другой проблемы со Random - если несколько экземпляров создаются почти в одно и то же время (в течение примерно 15 мс), они все будут возвращать одинаковые значения! Чтобы исправить это, мы создаем глобально-статический экземпляр Random для генерации начальных чисел, используемых каждым потоком.

Кстати, в вышеприведенной статье есть код, демонстрирующий обе эти проблемы с Random.

Ответ 3

Официальный ответ Microsoft - очень сильный не. Из http://msdn.microsoft.com/en-us/library/system.random.aspx#8:

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

Как описано в документах, существует очень неприятный побочный эффект, который может произойти, когда один и тот же случайный объект используется несколькими потоками: он просто перестает работать.

(т.е. существует условие гонки, которое при срабатывании возвращает значение из методов random.Next.... 'для всех последующих вызовов.)

Ответ 4

Нет, это не потокобезопасно. Если вам нужно использовать один и тот же экземпляр из разных потоков, вам необходимо синхронизировать использование.

Я действительно не могу понять, почему вам это нужно. Для каждого потока было бы более эффективным иметь собственный экземпляр класса Random.

Ответ 5

Другим потокобезопасным способом является использование ThreadLocal<T> следующим образом:

new ThreadLocal<Random>(() => new Random(GenerateSeed()));

Метод GenerateSeed() должен будет возвращать уникальное значение каждый раз, когда он вызывается, чтобы гарантировать, что последовательности случайных чисел уникальны в каждом потоке.

static int SeedCount = 0;
static int GenerateSeed() { 
    return (int) ((DateTime.Now.Ticks << 4) + 
                   (Interlocked.Increment(ref SeedCount))); 
}

Будет работать для небольшого количества потоков.

Ответ 6

Так как Random не является потокобезопасным, у вас должен быть один для потока, а не для глобального экземпляра. Если вы беспокоитесь о том, что эти несколько классов Random засеяны одновременно (т.е. На DateTime.Now.Ticks или таких), вы можете использовать Guid для каждого из них. Генератор .NET Guid имеет значительную длину для обеспечения неизбираемых результатов, следовательно:

var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))

Ответ 7

Для чего это стоит, это безопасный по потоку криптографически сильный RNG, который наследует Random.

Реализация включает в себя статические точки входа для удобства использования, они имеют те же имена, что и методы общего экземпляра, но имеют префикс "Get".

Вызов RNGCryptoServiceProvider.GetBytes - относительно дорогостоящая операция. Это смягчается с помощью внутреннего буфера или "пула", чтобы сделать менее частым и более эффективное использование RNGCryptoServiceProvider. Если в домене приложений несколько поколений, это можно рассматривать как служебные.

using System;
using System.Security.Cryptography;

public class SafeRandom : Random
{
    private const int PoolSize = 2048;

    private static readonly Lazy<RandomNumberGenerator> Rng =
        new Lazy<RandomNumberGenerator>(() => new RNGCryptoServiceProvider());

    private static readonly Lazy<object> PositionLock =
        new Lazy<object>(() => new object());

    private static readonly Lazy<byte[]> Pool =
        new Lazy<byte[]>(() => GeneratePool(new byte[PoolSize]));

    private static int bufferPosition;

    public static int GetNext()
    {
        while (true)
        {
            var result = (int)(GetRandomUInt32() & int.MaxValue);

            if (result != int.MaxValue)
            {
                return result;
            }
        }
    }

    public static int GetNext(int maxValue)
    {
        if (maxValue < 1)
        {
            throw new ArgumentException(
                "Must be greater than zero.",
                "maxValue");
        }
        return GetNext(0, maxValue);
    }

    public static int GetNext(int minValue, int maxValue)
    {
        const long Max = 1 + (long)uint.MaxValue;

        if (minValue >= maxValue)
        {
            throw new ArgumentException(
                "minValue is greater than or equal to maxValue");
        }

        long diff = maxValue - minValue;
        var limit = Max - (Max % diff);

        while (true)
        {
            var rand = GetRandomUInt32();
            if (rand < limit)
            {
                return (int)(minValue + (rand % diff));
            }
        }
    }

    public static void GetNextBytes(byte[] buffer)
    {
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }

        if (buffer.Length < PoolSize)
        {
            lock (PositionLock.Value)
            {
                if ((PoolSize - bufferPosition) < buffer.Length)
                {
                    GeneratePool(Pool.Value);
                }

                Buffer.BlockCopy(
                    Pool.Value,
                    bufferPosition,
                    buffer,
                    0,
                    buffer.Length);
                bufferPosition += buffer.Length;
            }
        }
        else
        {
            Rng.Value.GetBytes(buffer);
        }
    }

    public static double GetNextDouble()
    {
        return GetRandomUInt32() / (1.0 + uint.MaxValue);
    }

    public override int Next()
    {
        return GetNext();
    }

    public override int Next(int maxValue)
    {
        return GetNext(0, maxValue);
    }

    public override int Next(int minValue, int maxValue)
    {
        return GetNext(minValue, maxValue);
    }

    public override void NextBytes(byte[] buffer)
    {
        GetNextBytes(buffer);
    }

    public override double NextDouble()
    {
        return GetNextDouble();
    }

    private static byte[] GeneratePool(byte[] buffer)
    {
        bufferPosition = 0;
        Rng.Value.GetBytes(buffer);
        return buffer;
    }

    private static uint GetRandomUInt32()
    {
        uint result;
        lock (PositionLock.Value)
        {
            if ((PoolSize - bufferPosition) < sizeof(uint))
            {
                GeneratePool(Pool.Value)
            }

            result = BitConverter.ToUInt32(
                Pool.Value,
                bufferPosition);
            bufferPosition+= sizeof(uint);
        }

        return result;
    }
}

Ответ 8

В документации

Любые публичные статические (Shared in Visual Basic) члены этого типа являются потокобезопасными. Любые члены экземпляра не гарантируют безопасность потоков.

http://msdn.microsoft.com/en-us/library/system.random.aspx

Ответ 9

Для генератора случайных чисел, защищенного потоком, посмотрите RNGCryptoServiceProvider. Из документов:

Безопасность потока

Этот тип является потокобезопасным.

Ответ 10

ОБНОВЛЕНО: Это не так. Вам нужно либо повторно использовать экземпляр Random для каждого последовательного вызова с блокировкой какого-либо объекта "семафора" при вызове метода .Next(), либо использовать новый экземпляр с гарантированным случайным семенем для каждого такого вызова. Вы можете получить гарантированное разное семя, используя криптографию в .NET, как предложил Ясир.

Ответ 11

Традиционный поток локального хранилища может быть улучшен с помощью алгоритма блокировки для семян. Следующее было бесстыдно украдено из алгоритма Java (возможно, даже улучшение на нем):

public static class RandomGen2 
{
    private static readonly ThreadLocal<Random> _rng = 
                       new ThreadLocal<Random>(() => new Random(GetUniqueSeed()));

    public static int Next() 
    { 
        return _rng.Value.Next(); 
    } 

    private const long SeedFactor = 1181783497276652981L;
    private static long _seed = 8682522807148012L;

    public static int GetUniqueSeed()
    {
        long next, current;
        do
        {
            current = Interlocked.Read(ref _seed);
            next = current * SeedFactor;
        } while (Interlocked.CompareExchange(ref _seed, next, current) != current);
        return (int)next ^ Environment.TickCount;
   } 
}