【问题标题】:How to get random number in forked processes?如何在分叉进程中获取随机数?
【发布时间】:2020-01-26 22:59:20
【问题描述】:

我分叉了几个进程,我需要每个进程对随机选择的项目执行一项任务。 我发现分叉的进程选择了完全相同的随机数。 我尝试生成种子并调用 srand(),但没有太大帮助。实际上,我读过的大多数文档都建议避免使用 srand() “除非您确切知道自己在做什么”。

这是我的代码:

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use Time::HiRes qw(time);
use MongoDB;
#use Math::Random::Secure qw(rand);

my $num_clients = shift;
my $num_loops = 50_000_000;

sub test_forked_sub {
    my $sum=0;
    my $random_offset = rand(120);
    printf "time: %10.4f -  Random offset: %6.2f at pid: %s  \n", time(), $random_offset, $$;
    for (my $i=0; $i < $num_loops  ; $i++) {
        $sum += rand(120);
    }
}

my $nub_processes = 0;
for (my $client_id =0; $client_id < $num_clients; $client_id++ ) {
    if (my $pid = fork) {     #Parent
        $nub_processes++;
    }
    else {         # child
        die "cannot fork: $!" unless defined $pid;
        test_forked_sub();
        exit(0);
    }
}

while ($nub_processes){
    wait;
    $nub_processes--;
}

当我运行它时,我会在每个进程中得到完全相同的“随机”数字:

$ time ./test_rand_fork.pl 10
time: 1569510011.6891 -  Random offset:  46.64 at pid: 2091
time: 1569510011.6937 -  Random offset:  46.64 at pid: 2092
time: 1569510011.6987 -  Random offset:  46.64 at pid: 2093
time: 1569510011.7028 -  Random offset:  46.64 at pid: 2094
time: 1569510011.7070 -  Random offset:  46.64 at pid: 2095
time: 1569510011.7097 -  Random offset:  46.64 at pid: 2096
time: 1569510011.7144 -  Random offset:  46.64 at pid: 2097
time: 1569510011.7203 -  Random offset:  46.64 at pid: 2098
time: 1569510011.7230 -  Random offset:  46.64 at pid: 2099
time: 1569510011.7249 -  Random offset:  46.64 at pid: 2100

real    0m3.974s
user    0m32.955s
sys     0m1.444s

一个可能但丑陋的解决方案,它不是分叉。相反,要从我的 shell 运行多个实例,如下所示:

# shell processes
$ for i in `seq 1 10`; do  ./test_rand_fork.pl 1 &  done
time: 1569511908.7708 -  Random offset:   7.44 at pid: 4129
time: 1569511908.8070 -  Random offset:  19.50 at pid: 4131
time: 1569511908.8068 -  Random offset:  97.59 at pid: 4132
time: 1569511908.8073 -  Random offset:  14.51 at pid: 4133
time: 1569511908.8077 -  Random offset:  16.70 at pid: 4134
time: 1569511908.8080 -  Random offset: 108.63 at pid: 4138
time: 1569511908.8079 -  Random offset:  69.44 at pid: 4137
time: 1569511908.8080 -  Random offset:  83.25 at pid: 4136
time: 1569511908.8080 -  Random offset:  43.33 at pid: 4135
time: 1569511908.8203 -  Random offset:  33.82 at pid: 4139

由于“shell 进程”是一个丑陋的解决方案,我尝试使用 Math::Random::Secure。 以下是我根据需要获得随机性的结果,但它比“shell 进程”方法慢 50 倍:

# using: Math::Random::Secure qw(rand);
$ time ./test_rand_fork.pl 10
time: 1569510036.9331 -  Random offset: 112.48 at pid: 2128
time: 1569510036.9470 -  Random offset:  47.15 at pid: 2129
time: 1569510036.9501 -  Random offset:  20.77 at pid: 2130
time: 1569510036.9517 -  Random offset:  40.98 at pid: 2131
time: 1569510036.9521 -  Random offset:  13.84 at pid: 2132
time: 1569510036.9538 -  Random offset:  20.43 at pid: 2133
time: 1569510036.9543 -  Random offset:  48.48 at pid: 2134
time: 1569510036.9563 -  Random offset: 109.29 at pid: 2135
time: 1569510036.9579 -  Random offset:  70.30 at pid: 2136
time: 1569510036.9601 -  Random offset:  24.31 at pid: 2137

real    3m17.251s
user    32m31.129s
sys     0m1.054s

我需要的随机性不是出于安全目的,我只需要一个良好的传播。 有没有办法为分叉进程生成足够好的种子,并且仍然使用标准 rand() 或替代更快的方式?或者至少不需要安装额外的库?

【问题讨论】:

  • @PeterO。嗯,不——这讨论了一个不同的用例。当然,可以在forks 中使用rand,这是srand 的(极少数)目的之一(当相应地使用时)。在我的回答中查看示例

标签: perl random fork random-seed


【解决方案1】:

你想要的确实是srand。的确,通常人们不需要对它胡思乱想,但有一些用途

但是,在某些情况下,程序可能需要调用srand。一种是生成可预测的结果,通常用于测试或调试。在那里,您使用srand($seed),每次都使用相同的$seed

而且,正是为了你的目的

另一种情况是,您可能希望在 fork 之后调用 srand 以避免子进程与父进程共享相同的种子值(因此彼此共享)。

一个简单的例子

perl -wE'
    say "Parent $$ rand: ", rand;  
    for (1..4) { 
        $pid = fork // die "Cant fork: $!"; 
        if ($pid == 0) { srand(); say "\tchild $$ rand: ", rand; exit } 
    }'

打印

Parent 23375 rand: 0.502774688721733
        child 23376 rand: 0.914527430039929
        child 23377 rand: 0.97985713889739
        child 23378 rand: 0.127702740327553
        child 23379 rand: 0.187181734786467

上面的srand 可以解决问题;没有它,数字是相同的(因为它们必须是)。请注意,它也可以使用种子运行,这为我们提供了更好的诊断/测试控制。


注意事项,避免传播不良习惯。

在上面的快速程序中,子进程被系统清理(由 init 获取)。然而,一个人真的应该总是收获所以上面应该真的有

for (1..4) { 
    $pid = fork // die "Cant fork: $!"; 
    if ($pid == 0) { srand(); say "\tchild $$ rand: ", rand; exit } 
    push @procs, $pid;
}
for (@procs) { $gone = wait; say "$gone exited with $?" }

更好地使用waitpid 和非阻塞以获得更好的监控,也许在signal handler 中。

【讨论】:

  • 谢谢。我尝试在调用 rand 之前添加:srand()。仍然得到完全相同的随机数。
  • @Yuval 是的,你提到了——我不知道那里出了什么问题?
  • 我又看了一遍,我有一个错字,没有像我想的那样打电话给srand()。那么为什么人们实际上将随机库用于不安全的一般目的而不是简单地使用srand
  • @Yuval "为什么人们实际上使用 ... 而不是简单地使用 srand" -- 我不知道。很好地使用rand 正是您在“只需要良好的传播”时所说的好问题。使用种子(srand 代表 rand)在任何语言、环境或库中都可能有用(用于特定目的)。
猜你喜欢
  • 2017-06-04
  • 1970-01-01
  • 2012-03-16
  • 2012-04-12
  • 2018-01-22
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多