多线程黑客

时间:2010-08-22 05:40:41

标签: c++ synchronization

我正在编写一个应该有点快的数字程序,并且也是多线程的。我有一个表示数字的类,我想在其中使用随机数生成器。现在,我并不需要我的RNG成为真正的RNG,我只需要它生成0到NMAX之间均匀分布的整数。

所以我在课堂上有这个:

// use just an int here and forget about multithreading.
static uint32 rand = NMAX/4; 
// this will be called multithreadedly
static uint32 GetRand() { return rand = ( rand + 1 ) % NMAX; }

现在,在单线程世界中,这对我的目的来说完全没问题。

由于这是多线程的,我假设唯一可能发生的坏事是偶尔(比如<1%的时间)更新被删除。意思是两个线程读取rand,在寄存器中更新它,返回更新的值,然后用相同的值写两次。这完全没问题。

我的问题是,还有什么比这更糟糕的事情?我对每个线程使用自己的rand变量完全没问题,但这只是一个巨大的痛苦。我绝对不能做的是让它使每个类的实例使用自己的rand变量,因为它会占用过多的内存。

更新:

那么,为什么我要这样做呢?全文是一个使用1或2个字节的浮点类。所以它必须是快速的,这似乎是最好的方式。事实上,我认为我会将其从( rand + 1 ) % NMAX更新为类似( rand + [some prime] ) % NMAX的内容,因为它似乎效果更好。这是一个例子,其中一个更强大的解决方案需要更多的代码,使事情更少通用和更多的依赖性,使代码不那么清晰,更容易打破,所有的想法“应该使用适当的同步” 。

大多数情况下,我担心编译器可能会做一些奇怪的优化,因此rand的更新不仅会被删除,而且rand会变成完全垃圾。现在我考虑一下,即使也没关系(使用这个数字的方式),因为GetRand的下一次使用无论如何都是%NMAX,错误只会导致最多一次使用GetRand超出[0,NMAX]的给定范围。谢谢你的回答。

4 个答案:

答案 0 :(得分:1)

  

我对每个帖子都很好   使用自己的rand变量,但那是   只是一个巨大的痛苦才能实现。

这样做并不一定非常困难。一些编译器(例如GCC)支持thread-local storage,它允许每个线程拥有自己的给定变量副本。

话虽如此,我能想到的只有一个问题 - 除了你提到的问题 - 用你目前的做法。如果每个线程在不同的核心上运行,并且随机值存储在每个核心的非共享缓存中,则更新可能无法在核心之间无限期地传播。您可以使用memory barrier(可以通过使用锁创建)来避免这种情况,但这可能对性能有害。

答案 1 :(得分:1)

为了讨论的目的,我们假设以下实现:

  • 使用Mersenne Twister(mt19937),每次调用生成624个随机数批。
  • 您的类的每个实例(仅在单个线程中使用)从批处理中读取一个数字并递增全局索引计数器,以便下一次调用(来自任何实例)将获取批处理中的下一个数字。当全局索引到达数组末尾时,RNG将被锁定,并将生成一批新的624个随机数,然后重置全局索引。

我的改进建议是每个实例一次获取16个数字。这些16个数字不必存储(复制)在实例中:您只需将全局索引递增16(使其对其他实例不可用),以便实例可以逐个使用它们。

答案 2 :(得分:1)

  1. 您可以使用TLS,以便每个线程都有“自己的”变量。
  2. __declspec(thread) static uint32 rand = NMAX/4; 
    // this will be called multithreadedly
    static uint32 GetRand() { return rand = ( rand + 1 ) % NMAX; }
    
    1. 在您的特定情况下,很容易修复代码以使其成为线程安全的。
    2. static long rand = NMAX/4; 
      // this will be called multithreadedly
      static uint32 GetRand() { return InterlockedIncrement(&rand) % NMAX; }
      

答案 3 :(得分:0)

如果两个线程同时调用GetRand,则可能发生经典的非同步错误。例如,rand = 10.在两个线程调用GetRand之后,rand应该是12,但实际上它可能是11.如果你没问题,你可以保留这些代码而不做任何更改。但我认为使用同步更好,因为没有它,代码及其结果看起来有点奇怪。另一个编程可以认为是bug。

编辑。

rand = ( rand + 1 ) % NMAX;

最坏情况:两个或多个线程从内存中读取相同的rand变量rand。每个线程在本地进行计算(rand + 1)%NMAX。然后所有线程将相同的结果返回到内存中。这不会破坏rand变量值,这不会导致此变量超出范围,数字生成器将继续计算有效数字。