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



Это C# ' s Random.Next() метод является потокобезопасным?

618   11  

11 ответов:

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

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

StaticRandom
пересмотр случайности

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

С Random не является потокобезопасным, вы должны иметь один на поток, а не глобальный экземпляр. Если вы беспокоитесь об этих нескольких Random классы засеваются одновременно (т. е. DateTime.Now.Ticks или такой), вы можете использовать Guids, чтобы посеять каждый из них. Интернет .Сети Guid генератор прилагает значительные усилия для того чтобы обеспечить non-Repeatable результаты, таким образом:

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

для чего это стоит, вот потокобезопасный, криптографически сильный 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;
    }
}

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

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

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

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

Потокобезопасность

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

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

традиционный подход к локальному хранилищу потока можно улучшить с помощью алгоритма без блокировки для семени. Следующее было бесстыдно украдено из алгоритма 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;
   } 
}

Comments

    Ничего не найдено.