Programing

C # 난수 생성기는 스레드 안전합니까?

lottogame 2020. 11. 17. 07:38
반응형

C # 난수 생성기는 스레드 안전합니까?


C #의 Random.Next()방법은 스레드로부터 안전합니까?


Next스레드 안전성을 달성하기 위해 메서드 에서 특별히 수행 된 것은 없습니다 . 그러나 인스턴스 메서드입니다. Random여러 스레드에서 인스턴스를 공유하지 않으면 인스턴스 내 상태 손상에 대해 걱정할 필요가 없습니다. Random일종의 배타적 잠금을 유지하지 않고 다른 스레드 에서 단일 인스턴스를 사용하지 마십시오 .

Jon Skeet은이 주제에 대해 몇 가지 멋진 게시물을 올렸습니다.

StaticRandom
무작위성 재 방문

일부 해설자들이 언급했듯이, Random스레드 배타적이지만 동일하게 시드 된 다른 인스턴스를 사용하는 데 또 다른 잠재적 인 문제 가 있습니다. 서로의 근접. 이 문제를 완화하는 한 가지 방법은 마스터 Random인스턴스 (단일 스레드에 의해 잠김)를 사용하여 임의의 시드를 생성 Random하고 사용할 다른 모든 스레드에 대해 인스턴스를 초기화 하는 것입니다.


아니요, 여러 스레드에서 동일한 인스턴스를 사용하면 중단되고 모두 0을 반환 할 수 있습니다. 그러나 스레드로부터 안전한 버전 (를 호출 할 때마다 고약한 잠금이 필요하지 않음)을 만드는 Next()것은 간단합니다. 이 기사 의 아이디어에서 발췌 :

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

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

        return _local.Next();
    }
}

아이디어는 static Random각 스레드에 대해 별도의 변수 를 유지하는 것입니다 . 명백한 방법으로 그렇게하는 것은 실패하지만 다른 문제로 인해 Random-거의 동시에 (약 15ms 이내) 여러 인스턴스가 생성 되면 모두 동일한 값을 반환합니다! 이 문제를 해결하기 위해 Random각 스레드에서 사용하는 시드를 생성 하는 전역 정적 인스턴스를 만듭니다 .

그건 그렇고, 위의 기사에는 이러한 문제를 모두 보여주는 코드가 Random있습니다.


Microsoft의 공식적인 답변은 매우 강력 하지 않습니다 . 에서 http://msdn.microsoft.com/en-us/library/system.random.aspx#8 :

임의의 개체는 스레드로부터 안전하지 않습니다. 앱이 여러 스레드에서 Random 메서드를 호출하는 경우 동기화 개체를 사용하여 한 번에 하나의 스레드 만 난수 생성기에 액세스 할 수 있도록해야합니다. 스레드로부터 안전한 방식으로 Random 객체에 액세스 할 수없는 경우 난수를 반환하는 메서드를 호출하면 0이 반환됩니다.

문서에 설명 된대로 동일한 Random 개체가 여러 스레드에서 사용될 때 발생할 수있는 매우 불쾌한 부작용이 있습니다.

(즉, 트리거 될 때 'random.Next ....'메서드의 반환 값이 모든 후속 호출에 대해 0이되는 경쟁 조건이 있습니다.)


아니요, 스레드로부터 안전하지 않습니다. 다른 스레드에서 동일한 인스턴스를 사용해야하는 경우 사용을 동기화해야합니다.

그래도 왜 필요한지 모르겠습니다. 각 스레드가 Random 클래스의 자체 인스턴스를 갖는 것이 더 효율적입니다.


또 다른 스레드 안전 방법은 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))); 
}

적은 수의 스레드에서 작동합니다.


때문에 Random스레드 안전하지 않습니다, 당신은 스레드 당 하나가 아닌 전역 인스턴스가 있어야합니다. 이러한 여러 Random클래스가 동시에 시드되는 것이 걱정된다면 (예 :에 의해 DateTime.Now.Ticks) Guids를 사용 하여 각 클래스를 시드 할 수 있습니다. .NET Guid생성기는 반복 불가능한 결과를 보장하기 위해 상당한 길이로 이동합니다.

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


For a thread safe random number generator look at RNGCryptoServiceProvider. From the docs:

Thread Safety

This type is thread safe.


Reimplementation of BlueRaja's answer using ThreadLocal:

public static class ThreadSafeRandom
{
    private static readonly System.Random GlobalRandom = new Random();
    private static readonly ThreadLocal<Random> LocalRandom = new ThreadLocal<Random>(() => 
    {
        lock (GlobalRandom)
        {
            return new Random(GlobalRandom.Next());
        }
    });

    public static int Next(int min = 0, int max = Int32.MaxValue)
    {
        return LocalRandom.Value.Next(min, max);
    }
}

UPDATED: It is not. You need to either reuse an instance of Random on each consecutive call with locking some "semaphore" object while calling the .Next() method or use a new instance with a guaranteed random seed on each such call. You can get the guaranteed different seed by using cryptography in .NET as Yassir suggested.


The traditional thread local storage approach can be improved upon by using a lock-less algorithm for the seed. The following was shamelessly stolen from Java's algorithm (possibly even improving on it):

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;
   } 
}

참고URL : https://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe

반응형