如何制作一个c#线程安全的随机数生成器

时间:2016-07-22 15:26:35

标签: c# multithreading .net-core

我的代码中有一个循环

Parallel.For(0, Cnts.MosqPopulation, i => { DoWork() });

但是在DoWork()函数中,有多个对随机数生成器的调用,其定义如下:

public static class Utils
{
    public static readonly Random random = new Random();

}

它是静态实例,因此只播种一次。我可以在整个代码中使用它。

根据MSDN和其他stackoverflow线程,这不是线程安全的。事实上,我注意到有时我的代码中断和随机数生成器开始生成全零(根据MSDN文档)。

还有其他stackoverflow线程,但相当陈旧,实现速度很慢。我不能浪费时间来生成数字,因为程序是科学计算并运行数百个模拟。

自从2.0天以来,我没有使用.net,我不确定该语言是如何演变为能够快速,高效,线程安全的RNG。

以下是以前的主题:

Is C# Random Number Generator thread safe?

Correct way to use Random in multithread application

Fast thread-safe random number generator for C#

注意:因为我需要快速实现,所以我不能使用速度相当慢的RNGCryptoServiceProvider

注2:我没有最小的工作代码。我甚至不知道从哪里开始,因为我不知道线程安全如何工作或具有高级的c#知识。所以看起来我似乎要求一个完整的解决方案。

5 个答案:

答案 0 :(得分:2)

使用ThreadStatic属性和自定义getter,每个线程将获得一个Random实例。如果这是不可接受的,请使用锁。

public static class Utils
{
    [ThreadStatic]
    private static Random __random;

    public static Random Random => __random??(__random=new Random());
}

ThreadStatic属性不会在每个线程上运行初始化程序,因此您负责在访问者中执行此操作。另外想想你的种子初始化器,你可以使用像

这样的东西
new Random((int) ((1+Thread.CurrentThread.ManagedThreadId) * DateTime.UtcNow.Ticks) )

答案 1 :(得分:1)

我的实现结合了其他答案中的最佳方法(请参阅课堂文档中的设计说明)。

/// <summary>
/// DotNet Random is not ThreadSafe so we need ThreadSafeRandom.
/// See also: https://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe.
/// Design notes:
/// 1. Uses own Random for each thread (thread local).
/// 2. Seed can be set in ThreadSafeRandom ctor. Note: Be careful - one seed for all threads can lead same values for several threads.
/// 3. ThreadSafeRandom implements Random class for simple usage instead ordinary Random.
/// 4. ThreadSafeRandom can be used by global static instance. Example: `int randomInt = ThreadSafeRandom.Global.Next()`.
/// </summary>
public class ThreadSafeRandom : Random
{
    /// <summary>
    /// Gets global static instance.
    /// </summary>
    public static ThreadSafeRandom Global { get; } = new ThreadSafeRandom();

    // Thread local Random is safe to use on that thread.
    private readonly ThreadLocal<Random> _threadLocalRandom;

    /// <summary>
    /// Initializes a new instance of the <see cref="ThreadSafeRandom"/> class.
    /// </summary>
    /// <param name="seed">Optional seed for <see cref="Random"/>. If not provided then random seed will be used.</param>
    public ThreadSafeRandom(int? seed = null)
    {
        _threadLocalRandom = new ThreadLocal<Random>(() => seed != null ? new Random(seed.Value) : new Random());
    }

    /// <inheritdoc />
    public override int Next() => _threadLocalRandom.Value.Next();

    /// <inheritdoc />
    public override int Next(int maxValue) => _threadLocalRandom.Value.Next(maxValue);

    /// <inheritdoc />
    public override int Next(int minValue, int maxValue) => _threadLocalRandom.Value.Next(minValue, maxValue);

    /// <inheritdoc />
    public override void NextBytes(byte[] buffer) => _threadLocalRandom.Value.NextBytes(buffer);

    /// <inheritdoc />
    public override void NextBytes(Span<byte> buffer) => _threadLocalRandom.Value.NextBytes(buffer);

    /// <inheritdoc />
    public override double NextDouble() => _threadLocalRandom.Value.NextDouble();
}

答案 2 :(得分:0)

如果您了解并行运行的线程数,则可能有效:

Random rand = new Random();
var randomNums = Enumerable.Range(0, Cnts.MosqPopulation)
                           .Select(_ => rand.Next()).ToList();
Parallel.For(0, Cnts.MosqPopulation, i => 
{
    Random localRand = new Random(randomNums[i]);
    DoWork();
});

不确定所得分布与统一分布之间的区别是多么难以区分。

答案 3 :(得分:0)

我会考虑这样的事情:

private static int _tracker = 0;

private static ThreadLocal<Random> _random = new ThreadLocal<Random>(() => {
    var seed = (int)(Environment.TickCount & 0xFFFFFF00 | (byte)(Interlocked.Increment(ref _tracker) % 255));
    var random = new Random(seed);
    return random;
});

这些天我不是ThreadStatic的忠实粉丝。我们有比使用ThreadLocal更好的工具。只需在并行循环中使用_random.Value,它就会为每个线程提供一个新的Random

它结合了原子递增值以及使用Environemnt.TickCount的默认行为。递增值用于解决两个Random获得相同种子的问题。请注意,此方法仅允许创建255个randoms。如果您需要更多,请更改面具的大小。

正如您已经指出的那样,这不能用于安全目的。

答案 4 :(得分:-1)

您可以从Random继承以构建线程安全随机类

public class ThreadsafeRandom : Random
{
    private readonly object _lock = new object();

    public ThreadsafeRandom() : base() { }
    public ThreadsafeRandom( int Seed ) : base( Seed ) { }

    public override int Next()
    {
        lock ( _lock )
        {
            return base.Next();
        }
    }

    public override int Next( int maxValue )
    {
        lock ( _lock )
        {
            return base.Next( maxValue );
        }
    }

    public override int Next( int minValue, int maxValue )
    {
        lock ( _lock )
        {
            return base.Next( minValue, maxValue );
        }
    }

    public override void NextBytes( byte[ ] buffer )
    {
        lock ( _lock )
        {
            base.NextBytes( buffer );
        }
    }

    public override double NextDouble()
    {
        lock ( _lock )
        {
            return base.NextDouble();
        }
    }
}

并使用该类的实例

public static class Utils
{
    public static readonly Random random = new ThreadsafeRandom();

}