.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() 最優。兩者差距有一倍。
但是,指定隨機種子後,兩者就沒有差距了,這就有點讓人想不通了。