用于C#的快速线程安全随机数生成器

本文关键字:线程 安全 随机数生成器 用于 | 更新日期: 2023-09-27 18:24:54

我需要在多个正在运行的线程中快速生成随机浮点数。我尝试过使用System.Random,但它太慢了,无法满足我的需要,而且它在多个线程中返回相同的数字。(当我在一个线程中运行应用程序时,它工作得很好。)此外,我需要确保生成的数字在0到100之间。

以下是我现在正在尝试的:

number = random.NextDouble() * 100;

我该怎么办?

用于C#的快速线程安全随机数生成器

以下是我对它的看法(需要.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给了您相同的数字,那么您可能使用错误,要么是连续创建多个实例(意味着它们都将使用相同的种子,从而生成相同的序列),要么是在多个线程中使用单个实例(从而"破坏"该实例,因为它对多线程使用不安全)。

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

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

我使用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);
    }    
    }
}