如何使用rand_r函数并保证线程安全?

26

我正在尝试学习如何使用rand_r,在阅读了这个问题之后,我仍然有点困惑,能否有人看一下并指出我漏掉了什么?据我所知,rand_r需要一个指向某个值(或带有一些初始值的内存块)的指针,并在每次调用时使用它来生成新的数字。每个调用rand_r的线程都应该提供一个唯一的指针(或内存块),以获取不同线程之间的“真正随机”的数字。这就是为什么这样做的原因:

int globalSeed;

//thread 1
rand_r(&globalSeed);

//thread 2
rand_r(&globalSeed);

这是错误的使用方法。如果我有

int seed1,seed2;

//thread 1
rand_r(&seed1);

//thread 2
rand_r(&seed2);

这将是在线程之间生成“真正随机”数字的正确方法吗?

编辑:在阅读上述部分的答案后,有以下额外问题:

  1. 如果在线程1中我需要一个介于1到n之间的随机数,我应该这样做:(rand_r(&seed1) % (n-1)) + 1?还是有其他常见的做法?
  2. 如果种子的内存是动态分配的,那么这是正确或正常的吗?
1个回答

18

没错。你在第一种情况下所做的是绕过rand_r线程安全的特性。对于许多非线程安全函数,持久状态会在调用该函数时存储(这里是随机种子)。

对于线程安全变体,你实际上提供了一个线程特定的数据(seed1seed2),以确保状态不在线程之间共享。

请记住,这并不能使数字真正随机,它只是使序列相互独立。如果你使用相同的种子开始它们,那么两个线程中的随机序列可能会相同。

举个例子,假设你使用初始种子0得到一个随机序列2、3、5、7、11、13、17。使用共享种子,在来自两个不同线程的交替调用rand_r时,会出现这样的情况:

thread 1                thread 2
           <---  2
                 3 --->
           <---  5
                 7 --->
           <--- 11
                13 --->
           <--- 17

而这只是最好的情况 - 你可能会发现共享状态被破坏了,因为对它的更新可能不是原子的。

如果状态不是共享的(例如a和b代表两个不同的随机数源):

thread 1                thread 2
           <---  2a
                 2b --->
           <---  3a
                 3b --->
           <---  5a
                 5b --->
                 ::

一些线程安全的调用需要你提供特定于线程的状态,其他一些可以在内部创建特定于线程的数据(使用线程ID或类似信息),以便你永远不必担心它,可以在多线程和非多线程环境下使用完全相同的源代码。我个人更喜欢后者,因为这样会让我的生活更轻松。


编辑后问题的附加内容:

> 如果在线程1中,我需要1到n之间的随机数,我应该这样做'(rand_r(&seed1)%(n-1))+1',还是有其他常见的方法可以实现?

假设你想要一个值在1n之间的(包括边界),请使用(rand_r(&seed1) % n) + 1。第一个取模运算符可以得到一个从0n-1的值,然后加上1即可得到所需范围内的值。

> 如果对于种子来说将内存动态分配是正确或正常的吗?

只要你在使用它,种子就必须是持久的。你可以在线程中动态分配它,但也可以在线程的顶层函数中声明它。在这两种情况下,你都需要以某种方式将地址传递到较低级别(除非你的线程只是一个函数,这是不太可能的)。

你可以通过函数调用向下传递它,或者设置全局数组,在其中较低级别可以发现正确的种子地址。

或者,由于无论如何你都需要一个全局数组,你可以拥有一组种子而不是种子地址,较低级别可以使用这些种子来发现它们的种子。

在这两种情况下,你都可能会有一个包含线程ID作为键和要使用的种子的键值结构。然后,你将不得不编写自己的rand()例程,该例程将找到正确的种子并使用它调用rand_r()

这就是我更喜欢使用库例程的原因,这些例程在内部使用特定于线程的数据完成这项工作。


谢谢!所以最好的方法是对于不同的线程使用不同的种子和初始值,对吗? - derrdji
通常情况下,是的。但是在某些情况下,您可能希望使用共享状态(我的答案中的第一个图表),在这种情况下,您可以通过在互斥锁的保护下使用相同的状态调用 rand_r 来避免损坏的可能性。您不想做的一件事是使用相同的种子初始化两个序列,因为这些序列将是相同的。 - paxdiablo
是的,谢谢!我正要问一下是否可以共享一个并使用互斥锁保护 rand_r 调用。 - derrdji

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接