.net core 多线程下使用 Random 会出现bug

.net core 多线程下使用 Random 会出现的bug

先看原文:
Working with System.Random and threads safely in .NET Core and .NET Framework
https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/

我的结论:
.net 6 之前,如果习惯构建了一个静态的 Random 对象,然后业务代码里直接使用 random.Next() 方法,多线程竞争下,会有一定概率返回0。
.net 6 时,修正了这个默认创建时的多线程存在的bug。但又没完全解决。如果这个公共的 Random 方法初始化的适合使用了自定义随机种子,则还是会存在多线程竞争下出现返回0情况。
原文提供了在 .net 6 之前版本的一些解决办法,我的建议是使用 random.Next(1, 10000) 这样的方法生成随机数。

原文测试用代码片段[改]:


using System;
using System.Linq;
using System.Threading.Tasks;

public class Program {

    public static void Main() {
        // ⚠ This isn't safe, don't do it ⚠
        Random rng = new Random(); // create a shared Random instance
        Parallel.For(0, 10, x =>  // run in parallel
        {
            var numbers = new int[10_000];
            for (int i = 0; i < numbers.Length; ++i)
            {
                numbers[i] = rng.Next(); // Fetch 10,000 random numbers, to trigger the thread-safety issues
            }

            var numZeros = numbers.Count(x => x == 0); // how many issues were there?
            var avg = numbers.Average();
            Console.WriteLine($"Received {numZeros} zeroes  avg:{avg} {numbers[0]}");
        });
    }
}

当然好奇的脚步不止于此,为什么文章说的,大部分人习惯直接调用 .Next() 方法,而不是 .Next(min,max) 这样的方法呢?
我姑且认为是性能问题,于是增加了一个性能测试代码片段,用于生成 (1,100)区间内的随机数:

    public class RandomT
    {
        System.Random random1 = new System.Random(1);
        System.Random random2 = new System.Random(1);

        System.Random random11 = new System.Random();
        System.Random random22 = new System.Random();

        [Benchmark]
        public int RandomSeedByRange() => random1.Next(1, 100);

        [Benchmark]
        public int RandomSeedByMod() => random2.Next() % 100 + 1;

        [Benchmark]
        public int RandomDefaultByRange() => random11.Next(1, 100);

        [Benchmark]
        public int RandomDefaultByMod() => random22.Next() % 100 + 1;
    }

BenchmarkDotNet=v0.13.2, OS=ubuntu 20.04
Intel Xeon Gold 6133 CPU 2.50GHz, 1 CPU, 2 logical and 2 physical cores
.NET SDK=6.0.200
  [Host]     : .NET 6.0.2 (6.0.222.6406), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.2 (6.0.222.6406), X64 RyuJIT AVX2

Method Mean Error StdDev
RandomSeedByRange 15.096 ns 0.1449 ns 0.1210 ns
RandomSeedByMod 14.759 ns 0.1298 ns 0.1014 ns
RandomDefaultByRange 9.310 ns 0.0702 ns 0.0548 ns
RandomDefaultByMod 4.925 ns 0.1387 ns 0.2393 ns

性能果然是直接使用 .Next() 最优。两者差距有一倍。
但是,指定随机种子后,两者就没有差距了,这就有点让人想不通了。

posted on 2022-09-08 22:41  yahle  阅读(220)  评论(0编辑  收藏  举报