Вы Всё Ещё Используете new Random()? Начало
Распространённое в сети мнение об использовании Random: new Random() — зло!
Что new Random() использует системные часы для посева, и, если несколько экземпляров создаются в течение короткого промежутка времени, они генерируют идентичные результаты. Т.е. якобы:
for(var i = 0; i < 5; i++)
{
var random = new Random();
Console.WriteLine(random.Next(1,100));
}
Выведет: 42 42 42 42 42
В то время как:
for(var i = 0; i < 5; i++)
Console.WriteLine(Random.Shared.Next(1,100));
Выведет: 17 82 19 23 5
Откуда это взялось?
В .NET Framework new Random полагался на DateTime.Now.Ticks, который менялся примерно каждые 16 мс. Таким образом, в течение 16 миллисекунд вы получали одно и то же начальное значение посева, что каждый раз приводило к одному и тому же результату Next(…). Но это не так со времён .NET Core. Можете выполнить код выше и убедиться.
А что насчёт потокобезопасности?
Наивный пользователь Random, которому нужен один общий генератор случайных чисел, может поддаться искушению сделать что-то вроде следующего:
Random rng = new();
Parallel.For(0, 10, x =>
{
var value = rng.Next();
Console.WriteLine(value);
});
Несмотря на то, что этот код не является потокобезопасным, он, скорее всего, не выявит никаких проблем. До случая…
Random по умолчанию возвращает 0 при обнаружении проблемы потокобезопасности, поэтому следующий код выведет количество обнаруженных проблем:
Random rng = new();
Parallel.For(0, 10, x =>
{
var nums = new int[10_000];
for (int i = 0; i < nums.Length; ++i)
nums[i] = rng.Next();
Console.WriteLine(
$"{nums.Count(x => x == 0)} нулей");
});
Здесь уже интереснее. В .NET5 этот код показывает больше 9000 нулей в разных потоках, т.е. подавляющая часть вызовов Next сталкивается с проблемой потокобезопасности. В .NET8+ этого уже не происходит (по крайней мере, массово), однако, если предоставить посев при создании объекта Random, эта проблема вернётся:
Random rng = new(123);
Random.Shared.Next() использует потокобезопасный экземпляр (один на поток) через атрибут [ThreadStatic]. Таким образом, если вы используете последние версии .NET и вам не нужно предоставлять определённый посев, можно использовать Random.Shared (но и с new Random() скорее всего проблем не возникнет).
А что делать в .NET Framework? Об этом далее.
Окончание следует…
Источники:
- https://steven-giesel.com/blogPost/cc2a3f52-9d35-43d2-9687-c276a2de8e07/linkedin-are-you-still-using-new-random-everywhere
- https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/