Random работает на глобальных переменных и потому непотокобезопасен (и нет, threadvar были бы ещё хуже), я это одной упоротой личности пытался втолковывать на багтрекере, когда предлагал заменить алгоритм на что-нибудь нормальное. Потому что вихрь Мерсенна
СЛИШКОМ жирный, 2 Кб состояния — это несерьёзно, разобрано здесь:
https://www.pcg-random.org/posts/too-big-to-fail.html.
Сделай свой рандом как объект (type Random = record ... procedure Init(const seed); function NextUint32: uint32; ...), создавай его локально (var rng: Random;) и дёргай методы (rng.NextUint(10)) — это будет потокобезопасно.
В качестве внутреннего алгоритма рекомендую Xoshiro128**/32 или PCG64/32, вот мои реализации:
Код: Выделить всё
{$modeswitch advancedrecords}
type
Pcg64_32 = record
procedure Setup(const seed: uint64);
function Next: uint32;
// Even though delta is an unsigned integer, we can pass a signed integer to go backwards, it just goes "the long way round".
procedure Jump(const delta: uint64);
const
GoldenJump = uint64(11400714819323198485);
private
state: uint64;
const
Multiplier = uint64(6364136223846793005);
Increment = 3; // Может быть любым нечётным числом. В оригинале Increment неконстантен, но это вводит в заблуждение, см. https://pcg.di.unimi.it/pcg.php.
end;
{$push} {$rangechecks off} {$overflowchecks off}
procedure Pcg64_32.Setup(const seed: uint64);
var
i: integer;
begin
self.state := seed;
for i := 0 to 1 do Next; // По идее достаточно 1 раз, но тогда seed=0 выдаёт первым Next'ом 0, что некрасиво. :(
end;
function Pcg64_32.Next: uint32;
var
oldState: uint64;
begin
oldState := state;
state := oldState * Multiplier + Increment;
result := RorDWord(uint32(((oldState shr 18) xor oldState) shr 27), oldState shr 59);
end;
procedure Pcg64_32.Jump(const delta: uint64);
var
deltaLeft, accMul, accPlus, curMul, curPlus: uint64;
begin
// Это общий код для продвижения любого линейного конгруэнтного генератора на основании числа шагов (delta),
// состояния (state), множителя (Multiplier) и инкремента (Increment).
deltaLeft := delta;
curMul := Multiplier;
curPlus := Increment;
accMul := 1;
accPlus := 0;
while deltaLeft <> 0 do
begin
if deltaLeft and 1 <> 0 then
begin
accMul *= curMul;
accPlus := accPlus * curMul + curPlus;
end;
curPlus := (curMul + 1) * curPlus;
curMul *= curMul;
deltaLeft := deltaLeft shr 1;
end;
state := accMul * state + accPlus;
end;
{$pop}
type
// http://prng.di.unimi.it/
// Xoshiro128**: http://prng.di.unimi.it/xoshiro128starstar.c
// Xoshiro256**: http://prng.di.unimi.it/xoshiro256starstar.c
// Готовые полиномы прыжков, которых нет в оригинальных реализациях, взяты отсюда:
// http://peteroupc.github.io/jump.html
//
// Для Xoshiro128** — 2^32, 2^48, (2^64), (2^96).
// Для Xoshiro256** — 2^32, 2^48, 2^64, 2^96, (2^128), 2^160, (2^192), 2^224.
//
// Оттуда же полиномы для прыжков на (период_генератора / φ), где φ — золотое сечение.
// Утверждается, что повторные применения таких прыжков, врапающихся через полный период, дадут состояния,
// отстоящие друг от друга на расстояние, близкое к максимально возможному.
//
// Там же описывается способ выполнять произвольные прыжки, но это очень сложна.
// The state must be seeded so that it is not everywhere zero.
Xoshiro128ss_32 = record
type
StateVec = array[0 .. 3] of uint32;
procedure Setup(const seed: uint64);
function Next: uint32; {$ifdef inline_nexts} inline; {$endif}
procedure Jump(const poly: StateVec);
private
s: StateVec;
public const
PolyGolden: StateVec = (uint32($d1cf96b6), uint32($8fda8fd5), uint32($59016992), uint32($338b58d0)); // 0x338b58d0590169928fda8fd5d1cf96b6
Poly2p32: StateVec = (uint32($f7afe108), uint32($f3be07b8), uint32($730b948d), uint32($0f8aed94)); // 0xf8aed94730b948df3be07b8f7afe108
Poly2p48: StateVec = (uint32($cb56667c), uint32($87a4583d), uint32($dec5bb9a), uint32($deaa4ca2)); // 0xdeaa4ca2dec5bb9a87a4583dcb56667c
Poly2p64: StateVec = (uint32($8764000b), uint32($f542d2d3), uint32($6fa035c3), uint32($77f2db5b)); // 0x77f2db5b6fa035c3f542d2d38764000b
Poly2p96: StateVec = (uint32($b523952e), uint32($0b6f099f), uint32($ccf5a0ef), uint32($1c580662)); // 0x1c580662ccf5a0ef0b6f099fb523952e
end;
{$push} {$rangechecks off} {$overflowchecks off}
procedure Xoshiro128ss_32.Setup(const seed: uint64);
var
i: integer;
begin
if seed = 0 then
begin
Setup(High(uint64));
exit;
end;
s[0] := Lo(seed);
s[1] := Hi(seed);
s[2] := Lo(seed);
s[3] := Hi(seed);
// У Xoshiro проблемы с декорреляцией, затягивающиеся на примерно 30 шагов: https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html
// Поэтому, чтобы seed=N и seed=N+1 разошлись, желательно промотать хотя бы на 30 состояний вперёд, а лучше больше.
for i := 0 to 63 do Next;
end;
function Xoshiro128ss_32.Next: uint32;
{var
t: uint32;
begin
result := RolDWord(s[1] * 5, 7) * 9;
t := s[1] shl 9;
s[2] := s[2] xor s[0];
s[3] := s[3] xor s[1];
s[1] := s[1] xor s[2];
s[0] := s[0] xor s[3];
s[2] := s[2] xor t;
s[3] := RolDWord(s[3], 11);
end;}
// Free Pascal очень тупой, вручную расписанный вариант намного быстрее.
var
s0, s1, s2, s3: uint32;
begin
s0 := s[0];
s1 := s[1];
s2 := s[2] xor s0;
s3 := s[3] xor s1;
result := RolDWord(s1 * 5, 7) * 9;
s[0] := s0 xor s3;
s[1] := s1 xor s2;
s[2] := s2 xor (s1 shl 9);
s[3] := RolDWord(s3, 11);
end;
procedure Xoshiro128ss_32.Jump(const poly: StateVec);
var
ns: StateVec;
i, ip, b: SizeInt;
begin
FillChar(ns, sizeof(ns), 0);
for ip := 0 to High(poly) do
for b := 0 to bitsizeof(uint32) - 1 do
begin
if poly[ip] and (uint32(1) shl b) <> 0 then
for i := 0 to High(ns) do
ns[i] := ns[i] xor s[i];
Next;
end;
s := ns;
end;
{$pop}