【问题标题】:Fast thread-safe random number generator for C#用于 C# 的快速线程安全随机数生成器
【发布时间】:2012-03-07 18:46:14
【问题描述】:

我需要跨多个正在运行的线程快速生成随机浮点数。我试过使用System.Random,但它对我的需要来说太慢了,它在多个线程中返回相同的数字。 (当我在单线程中运行我的应用程序时它工作正常。)另外,我需要确保生成的数字在 0 到 100 之间。

这是我现在正在尝试的:

number = random.NextDouble() * 100;

我应该尝试什么?

【问题讨论】:

  • 如果 random 总是给你相同的数字,你可能没有正确使用它。另请注意,Random 不是线程安全的。
  • 另请注意,生成真正的随机数是一件大事:random.org/randomness
  • 非常快速且线程安全:return 4;
  • 在“并行随机数生成器”主题上搜索 O(10^6) 引用。我建议 OP 做一些研究。

标签: c# performance random parallel-processing generator


【解决方案1】:

这是我的看法(需要 .net 4.0):

public static class RandomGenerator
{
    private static object locker = new object();
    private static Random seedGenerator = new Random(Environment.TickCount);

    public static double GetRandomNumber()
    {
        int seed;

        lock (locker)
        {
            seed = seedGenerator.Next(int.MinValue, int.MaxValue);
        }

        var random = new Random(seed);

        return random.NextDouble();
    }
}

还有一个测试来检查 1000 次迭代中每个值是否唯一:

[TestFixture]
public class RandomGeneratorTests
{
    [Test]
    public void GetRandomNumber()
    {
        var collection = new BlockingCollection<double>();

        Parallel.ForEach(Enumerable.Range(0, 1000), i =>
        {
            var random = RandomGenerator.GetRandomNumber();
            collection.Add(random);
        });

        CollectionAssert.AllItemsAreUnique(collection);
    }
}

我不保证它永远不会返回重复值,但我已经运行了 10000 次迭代的测试并且它通过了测试。

【讨论】:

  • 这不是线程安全的,因为它在所有线程之间共享一个 Random 实例 -- seedGenerator。这迟早会在多线程环境中中断。
  • 好点,我错过了seedGenerator.Next() 周围的锁。我已经更新了示例。
  • 在生成1..n范围内的数字时,连续两次生成值x的几率应该是1/n,而不是零。
【解决方案2】:

如果Random 为您提供相同的数字,那么您可能使用不正确,或者通过连续创建许多实例(意味着它们都将使用相同的种子并因此生成相同的序列),或者通过跨多个线程使用单个实例(从而“破坏”该实例,因为它对于多线程使用不安全)。

如果在单线程中运行时Random 的速度和随机性对您来说足够好,那么您可以尝试将其包装在ThreadLocal&lt;T&gt; 中,以确保在多线程场景中每个线程都有一个单独的实例:

var number = _rng.Value.NextDouble() * 100;

// ...

private static int _staticSeed = Environment.TickCount;
private static readonly ThreadLocal<Random> _rng = new ThreadLocal<Random>(() =>
    {
        int seed = Interlocked.Increment(ref _staticSeed) & 0x7FFFFFFF;
        return new Random(seed);
    });

【讨论】:

    【解决方案3】:

    我使用 windows cryptoAPI 来获得好的随机数。为了提高性能,我对 8KB 的随机数据块进行了一次调用,并从中分配数字,而不是为每个数字调用 cryptoAPI。与正常随机相比,不确定性能到底如何。但随机化要好得多(有关 Windows CryptoAPI 的详细信息,请查看互联网)

    这是代码;

    // UNIT RandomNumberGeneratorBase
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace FastLibrary
    {
        public abstract class RandomNumberGeneratorBase
    {    
            private int _byteBufSize;
            private byte[] _buf;
            private int _idx;
            private int _lastsize;
    
            public RandomNumberGeneratorBase(int bufSize = 8092)
        {    
                _byteBufSize = bufSize;
                _buf = new byte[_byteBufSize];
                _idx = _byteBufSize;
            }
    
            protected abstract void GetNewBuf(byte[] buf);
    
            private void CheckBuf(int bytesFreeNeeded = 1)
            {    
                _idx += _lastsize;
                _lastsize = bytesFreeNeeded;
                if (_idx + bytesFreeNeeded < _byteBufSize) { return; }
                GetNewBuf(_buf);
                _idx      = 0;
            }
    
            public byte GetRandomByteStartAtZero(int belowValue)
           {    
             return (byte)(Math.Round(((double)GetRandomByte() * (belowValue - 1)) / 255));
           }    
    
            public int GetRandomIntStartAtZero(int belowValue)
           {    
                return (int)(Math.Round(((double)GetRandomUInt32() * (double)(belowValue - 1)) / (double)uint.MaxValue));
           }    
    
            public byte GetRandomByte()
        {    
                CheckBuf();
            return _buf[_idx];
        }    
    
            public bool GetRandomBool()
        {    
                CheckBuf();
            return _buf[_idx] > 127;
        }    
    
            public ulong GetRandomULong()
        {    
                CheckBuf(sizeof(ulong));
            return BitConverter.ToUInt64(_buf, _idx);
        }    
    
            public int GetRandomInt()
        {    
                CheckBuf(sizeof(int));
            return BitConverter.ToInt32(_buf, _idx);
        }    
    
            /// <summary>
            ///     Double from 0 to 1 (might be zero, will never be 1)
            /// </summary>
            public double GetRandomDouble()
        {    
                return GetRandomUInt32() / (1d + UInt32.MaxValue);
        }    
    
            /// <summary>
            ///     Float from 0 to 1 (might be zero, will never be 1)
            /// </summary>
            public float GetRandomFloat()
        {    
                return GetRandomUInt32() / (1f + UInt32.MaxValue);
        }    
    
            public uint GetRandomUInt32()
        {    
                CheckBuf(sizeof(UInt32));
                return BitConverter.ToUInt32(_buf, _idx);
        }    
        }    
    }    
    
    // UNIT StrongRandomNumberGenerator
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    
    namespace FastLibrary
    {
        public sealed class StrongRandomNumberGenerator : RandomNumberGeneratorBase
    {    
            private RNGCryptoServiceProvider _rnd;
    
            public StrongRandomNumberGenerator()
        {    
                _rnd = new RNGCryptoServiceProvider();
        }    
    
            protected override void GetNewBuf(byte[] buf)
        {    
                _rnd.GetBytes(buf);
        }    
    
        }
    }    
    

    【讨论】:

    • 我想知道 - 你是如何测试它的?我创建了一个简单的测试方法,根据该方法,当您生成一百万个随机数时,您的代码比默认的 Random 类 - pastebin.com/BGc66gZx 更糟糕。不仅速度较慢,而且会产生两倍的重复...
    • 骗子是因为一个小错误。代码已更新。根据我 7 年前在网上看到的内容,cryptoaapi 的随机性应该比 random.next 好得多。没有链接,对不起。性能不是我需要更好的随机性的主要问题。
    • 您的测试代码将花费大量时间添加到列表并扩展其内存。仅出于性能测试,请勿将数字添加到列表中并比较这些时间。
    • 在第 13 分钟左右观看此视频。它是一个消息来源,告诉我们不要使用 random.next 而是使用 cryptoapi。 youtu.be/ySQl0NhW1J0?t=788
    • 为了测试,我制作了 x 个存储桶并生成了数百万个应该平均分布在存储桶中的数字,并在最后检查了存储桶之间的分布。说一个 1 到 6 的桶,并重复生成一个骰子的数字。所有桶中的计数应该非常接近。该错误不幸地在此测试中幸存下来。我后来在我认为的代码中添加一些功能时发现了它。 (当时忘记更新stackoverflow。对不起:)
    猜你喜欢
    • 2012-02-07
    • 1970-01-01
    • 2020-01-31
    • 1970-01-01
    • 1970-01-01
    • 2017-04-20
    • 1970-01-01
    • 1970-01-01
    • 2016-02-21
    相关资源
    最近更新 更多