web-dev-qa-db-ja.com

C#Random Number Generatorスレッドセーフですか?

C#の Random.Next() メソッドはスレッドセーフですか?

65
noone

Nextメソッドでは、スレッドセーフを実現するために特別な処理は行われません。ただし、これはインスタンスメソッドです。 Randomのインスタンスを異なるスレッド間で共有しない場合、インスタンス内の状態の破損を心配する必要はありません。何らかの排他ロックを保持せずに、異なるスレッド間でRandomの単一のインスタンスを使用しないでください。

Jon Skeetには、このテーマに関するいくつかの素敵な投稿があります。

StaticRandom
ランダム性の再検討

一部のコメンテーターが指摘しているように、スレッド専用の異なるRandomのインスタンスを使用する場合、別の潜在的な問題があります。互いの時間的近接。その問題を軽減する1つの方法は、マスターRandomインスタンス(単一のスレッドによってロックされている)を使用してランダムシードを生成し、使用する他のすべてのスレッドの新しいRandomインスタンスを初期化することです。

25
Mehrdad Afshari

いいえ、複数のスレッドから同じインスタンスを使用すると、すべての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からの公式の回答は、verystrongnoです。 http://msdn.Microsoft.com/en-us/library/system.random.aspx#8 から:

ランダムオブジェクトはスレッドセーフではありません。アプリが複数のスレッドからRandomメソッドを呼び出す場合、同期オブジェクトを使用して、一度に1つのスレッドのみが乱数ジェネレーターにアクセスできるようにする必要があります。 Randomオブジェクトがスレッドセーフな方法でアクセスされることを保証しない場合、乱数を返すメソッドの呼び出しは0を返します。

ドキュメントで説明されているように、同じRandomオブジェクトが複数のスレッドで使用されると、非常に厄介な副作用が発生する可能性があります。つまり、動作が停止するだけです。

(つまり、トリガーされると、 'random.Next ....'メソッドからの戻り値が後続のすべての呼び出しで0になる競合状態があります。)

17
JSWork

いいえ、スレッドセーフではありません。異なるスレッドから同じインスタンスを使用する必要がある場合は、使用法を同期する必要があります。

あなたがそれを必要とする理由はわかりません。各スレッドがRandomクラスの独自のインスタンスを持つ方が効率的です。

14
Guffa

別のスレッドセーフな方法は、次のように_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))); 
}
_

少数のスレッドで機能します。

8
rjroy

Randomはスレッドセーフではないため、グローバルインスタンスではなく、スレッドごとに1つ必要です。これらの複数のRandomクラスが同時にシードされることを心配している場合(つまり、DateTime.Now.Ticksなど)、Guidsを使用してそれぞれをシードできます。 .NET Guidジェネレーターは、再現性のない結果を保証するためにかなりの長さになります。したがって、

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

その価値があるのは、Randomを継承する、スレッドセーフで暗号的に強力なRNGです。

実装には、使いやすいように静的なエントリポイントが含まれています。これらはパブリックインスタンスメソッドと同じ名前ですが、「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;
    }
}
4
Jodrell

ドキュメントごと

この型のpublic static(Visual BasicではShared)メンバーは、スレッドセーフです。インスタンスメンバーは、スレッドセーフであるとは限りません。

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

2
Seattle Leonard

スレッドセーフな乱数ジェネレーターについては、 RNGCryptoServiceProvider をご覧ください。ドキュメントから:

スレッドセーフティ

このタイプはスレッドセーフです。

1
Hannoun Yassir