【发布时间】:2020-11-20 22:44:54
【问题描述】:
我需要一个线程安全的高性能随机数生成器。我只需要值类型中的随机字节(现在是ulong),而不是在范围内。我使用了 C# 内置的 Random 类,但它有点慢而且不是线程安全的。
后来我转向了 XORShift 函数,它实际上工作得很好,但为了实现线程安全,我需要将计算放在 lock 中,这会大大降低性能。
我用来生成随机ulong 的内容如下:
public class Rand
{
ulong seed = 0;
object lockObj = new object();
public Rand()
{
unchecked
{
seed = (ulong)DateTime.Now.Ticks;
}
}
public Rand(ulong seed)
{
this.seed = seed;
}
public ulong GetULong()
{
unchecked
{
lock (lockObj)
{
ulong t = 0;
t = seed;
t ^= t >> 12;
t ^= t << 25;
t ^= t >> 27;
seed = t;
return t * 0x2545F4914F6CDD1D;
}
}
}
}
这工作得很好而且很快,但是如果从 200 个并发线程中调用,锁定会使它花费大约 1-2us,否则计算在 100ns 下完成。
如果我删除锁定,则两个线程有可能采用相同的种子并计算相同的随机数,这对我的目的不利。如果我删除ulong t 声明并直接在种子上工作,那么为两个并发调用生成相同随机数的机会很小,但也有可能将值从值范围移出, 就像t << 25 会被不同的线程连续多次调用而不进行旋转,它会变成简单的0。
我认为正确的方法是,如果有一个共享值可以被任何并发调用更改并在计算方法中使用该值,因为这些值是原子的(至少对于 CPU 内核而言)它不是如果许多计算同时使用它,则会出现问题,但是如果该值从位范围移出,则会出现问题。
有什么好的办法可以解决这个问题吗?如有任何帮助,我将不胜感激。
编辑:好的,我忘了说我无法控制线程,因为异步任务正在调用这个函数,所以线程是从线程池中随机出现的,使用线程 ID 也是一个没有解决方案,因为有一个特定线程可能永远不会再次调用此方法,并且为该 ID 保留一个实例并不是一件好事。
【问题讨论】:
-
不是,但 xorshift 算法本身与语言无关。我本可以发布一个伪代码或 C 代码,但这就是我正在使用的,它在技术上是有效的,并且易于阅读,这就是我复制它的原因。 - 但无论如何你是对的,我删除了 C 标签。
-
为每个线程提供自己的 Rand 实例是显而易见的方法。如果可以,请避免使用 ThreadLocal。您只需要确保使用另一个(锁定的)实例正确初始化种子即可。
-
@HansPassant 我编辑了问题,请阅读最后的“编辑”。
-
为什么不直接省略锁定和临时变量
t?让线程同时洗牌seed。它可能比现在更加随机。 -
好吧,如果你想那样做,你就不能避免使用 ThreadLocal。 “无控制”很少是编写高性能代码的好方法。
标签: c# multithreading random