.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() 最优。两者差距有一倍。
但是,指定随机种子后,两者就没有差距了,这就有点让人想不通了。