为什么fork后rand()生成的随机数不够随机?

10
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    int i =10;
    /* initialize random seed:  */
    srand(time(NULL));
    while(i--){
        if(fork()==0){
            /* initialize random seed here does not make a difference:
            srand(time(NULL));
             */
            printf("%d : %d\n",i,rand());
            return;
        }
    }
    return (EXIT_SUCCESS);
}

打印相同的(每次运行时不同)数字10次 - 期望是什么? 我有一段更复杂的代码,其中每个分叉进程依次运行 - 没有差异

7个回答

19

输出必须相同。如果两个进程使用相同的种子进行随机数生成并且每个进程调用了一次rand,则它们必须获得相同的结果。这就是拥有种子的全部意义。所有进程都使用相同的种子调用srand(因为只调用了一次srand),并且它们都调用一次rand,所以它们必须获得相同的结果。

取消注释srand不会有任何影响,因为除非秒数发生改变,否则它们仍将给出相同的种子。你可以这样做:

srand(time(NULL) ^ (getpid()<<16));

您介意简要解释一下 ^ (getpid()<<16) 的底层操作是什么吗? - Soner from The Ottoman Empire
time(NULL) 确保您每秒获得不同的值。(getpid()<<16) 增加了每个进程获得不同值的可能性,因为进程 ID 通常不会那么快地被重新使用。 - David Schwartz
我明白您说的那部分,我的意思是为什么要使用XOR操作而不是&或|?我正在处理那个(数学)部分。 - Soner from The Ottoman Empire
1
@snr XOR 确保任何一侧的更改都会改变结果。8&1 和 8&2 相同,因此 & 不适用。9|1 和 9|9 相同,因此 | 不适用。 - David Schwartz

5
如果您的代码运行速度足够快,srand() 可能会为每个子进程种植相同的时间种子。而 time() 每秒才会变化一次。

5
rand()函数是一个伪随机数生成器,这意味着生成的数字序列是确定性的,仅取决于提供的种子。
因为您正在分叉相同的进程10次,所以随机数生成器的状态对于每个子进程都是相同的。下次调用rand()时,您将获得相同的值。
通过在子进程中调用srand(time(NULL)),您有可能帮助改善,但是time()的粒度只有1秒,因此所有的子进程可能都在同一秒内开始。使用相同的值进行种子处理会生成相同的伪随机序列。
您可以尝试使用依赖于子进程编号的值进行种子处理:
srand(time(NULL) - i*2);

(在fork循环期间,我使用i*2来应对time()每秒前进的情况。)

总体来说是个好主意,但最好将子数以更重要的方式纳入其中。可以使用 time(NULL) + 100 * i 或类似的方法来确保一两秒的差异不会影响种子。 - sarnold
好主意,我喜欢David Schwartz使用子进程ID的想法。 - Greg Hewgill
是的,我真的很喜欢他如何在_high_中混合它,但是对于使用clone(2)而不是fork(2)的应用程序,它们都可以拥有相同的“pid”但具有不同的“i”值。这是一个微妙的差别,可能永远不会影响任何人... - sarnold
PID的好处在于,即使运行速度非常快,结果也永远不会相同。 - Mr_and_Mrs_D

3
即使在循环内添加srand(time(NULL));(您已经注释的if块中的行)也无法使程序有所不同,因为现代计算机可以非常快地执行整个块,而time以秒计数。来自man页面的信息如下:

time()返回自“Epoch”以来的秒数...

如果在while循环中的if语句后添加sleep(1);并取消注释srand调用,则结果将有所不同,因为time现在会返回不同的值,因为一秒钟过去了。
然而,使用不同的种子值而不是等待更合适。像i这样的东西会是一个好主意,因为它对于循环的每次迭代都是唯一的。

1

在创建子进程时,您没有重新播种。随机数生成器的状态完全相同。

即使在子进程中再次播种,也会使用时间进行播种,精度为+/- 1秒。当您分叉时,所有这些都会在不到一秒钟内发生。

尝试使用不同且更随机的内容进行播种。


0
这是因为所有程序都使用相同的值进行种子化(在while循环之外)。一旦您分叉了新程序,您应该再次进行种子化,否则两个程序将产生相同的序列。

0

这解决了问题:

srand48((long int)time(NULL));
i= (lrand48()/rand()+1) % 123

我还没有用fork测试过,但在for循环内调用100次它可以工作。

使用pid号进行种子初始化。这是一个有点困难的问题。

在某个页面上写道:"这个函数可以工作:srand(time(0)+getpid()); 但我必须在case 0即子进程中调用它。"


我不理解你的英语。 - Mr_and_Mrs_D

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