C#の Random.Next()
メソッドはスレッドセーフですか?
Next
メソッドでは、スレッドセーフを実現するために特別な処理は行われません。ただし、これはインスタンスメソッドです。 Random
のインスタンスを異なるスレッド間で共有しない場合、インスタンス内の状態の破損を心配する必要はありません。何らかの排他ロックを保持せずに、異なるスレッド間でRandom
の単一のインスタンスを使用しないでください。
Jon Skeetには、このテーマに関するいくつかの素敵な投稿があります。
一部のコメンテーターが指摘しているように、スレッド専用の異なるRandom
のインスタンスを使用する場合、別の潜在的な問題があります。互いの時間的近接。その問題を軽減する1つの方法は、マスター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からの公式の回答は、verystrongnoです。 http://msdn.Microsoft.com/en-us/library/system.random.aspx#8 から:
ランダムオブジェクトはスレッドセーフではありません。アプリが複数のスレッドからRandomメソッドを呼び出す場合、同期オブジェクトを使用して、一度に1つのスレッドのみが乱数ジェネレーターにアクセスできるようにする必要があります。 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
はスレッドセーフではないため、グローバルインスタンスではなく、スレッドごとに1つ必要です。これらの複数のRandom
クラスが同時にシードされることを心配している場合(つまり、DateTime.Now.Ticks
など)、Guid
sを使用してそれぞれをシードできます。 .NET Guid
ジェネレーターは、再現性のない結果を保証するためにかなりの長さになります。したがって、
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
その価値があるのは、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;
}
}
ドキュメントごと
この型のpublic static(Visual BasicではShared)メンバーは、スレッドセーフです。インスタンスメンバーは、スレッドセーフであるとは限りません。
スレッドセーフな乱数ジェネレーターについては、 RNGCryptoServiceProvider をご覧ください。ドキュメントから:
スレッドセーフティ
このタイプはスレッドセーフです。