Ruby 使用带srand和rand的并行gem实现惊人的输出

Ruby 使用带srand和rand的并行gem实现惊人的输出,ruby,random,parallel-processing,Ruby,Random,Parallel Processing,我正在使用ruby 2.4.1+parallel 1.11.2。我在irb中运行以下程序: require 'parallel' srand(1) Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" } 我的理解是,当指定了in_进程时,Parallel.map分叉进程,然后执行循环体。考虑到这一点,我希望两个进程都具有相同的全局状态,因此我希望两个进程都输出相同的随机数。

我正在使用ruby 2.4.1+parallel 1.11.2。我在irb中运行以下程序:

require 'parallel'
srand(1)
Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" }
我的理解是,当指定了
in_进程
时,
Parallel.map
分叉进程,然后执行循环体。考虑到这一点,我希望两个进程都具有相同的全局状态,因此我希望两个进程都输出相同的随机数。然而,我得到的是:

irb(main):003:0> Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" }
in process 1; rand => 0.48721687007281356
in process 0; rand => 0.7502824863668285
=> [nil, nil]
对于记录,如果我执行
srand(1)
,然后执行
rand
,则得到0.417022004702574,因此似乎两个进程都没有得到我设置的随机数种子。我可以通过在循环中设置随机数种子来获得我想要的行为,但是在我这样做之前,我试图理解为什么将种子放在循环外是不起作用的

我想弄明白这种情况。这种行为是不是某种程度上特定于随机数生成器,因此我不一定会有与其他对象相同的问题(即,预期的共享初始状态,但没有得到它)?或者,并行是否真的与普通的
fork
系统调用没有相同的效果

关于与
并行的in_进程
的文档让我相信它的行为类似于
fork
,但这里的情况似乎并非如此,因此我感到惊讶

编辑:更多的实验表明,使用
Process.fork
时会出现相同的行为,因此问题与
fork
有关,而不是与并行gem有关

$ cat foo.rb
srand(1)
pid = Process.fork
if !pid
then puts "child says rand => #{rand}"
else puts "parent says rand => #{rand}"
Process.wait(pid)
end

$ ruby foo.rb
parent says rand => 0.417022004702574
child says rand => 0.7054895237863591
编辑:进一步调查似乎表明,此处相关的选项
instation:true
。在父进程中访问变量时,
isolation:true
似乎具有所需的效果

irb(main):037:0> foo = 1;
irb(main):038:0* Parallel.map([0, 1, 2, 3, 4, 5], in_processes: 2) { |i| puts "in process #{i}; foo = #{foo}"; foo = foo + 1 }
in process 0; foo = 1
in process 2; foo = 2
in process 3; foo = 3
in process 4; foo = 4
in process 5; foo = 5
in process 1; foo = 1
=> [2, 2, 3, 4, 5, 6]
irb(main):039:0> foo = 1;
irb(main):040:0* Parallel.map([0, 1, 2, 3, 4, 5], in_processes: 2, isolation: true) { |i| puts "in process #{i}; foo = #{foo}"; foo = foo + 1 }
in process 1; foo = 1
in process 0; foo = 1
in process 2; foo = 1
in process 3; foo = 1
in process 4; foo = 1
in process 5; foo = 1
=> [2, 2, 2, 2, 2, 2]
但是
隔离:true
似乎对
兰德
没有预期的效果。还是不明白那里发生了什么

irb(main):032:0> srand(1);
irb(main):033:0* Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" }
in process 0; rand => 0.6837528723167413
in process 1; rand => 0.1469087219402977
=> [nil, nil]
irb(main):034:0> srand(1);
irb(main):035:0* Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" }
in process 0; rand => 0.7906373908366543
in process 1; rand => 0.8807214141308389
=> [nil, nil]
不要使用依赖于全局状态的
rand()
。请改用
SecureRandom
,或者如果需要可预测的序列,
Random

seed = 1
generators = Array.new(2) { Random.new(seed) }

Parallel.map([0, 1], in_processes: 2) do |i|
  puts "in process #{i}; rand => #{generators[i].rand}"
end
这将提供一致的输出:

in process 1; rand => 0.417022004702574
in process 0; rand => 0.417022004702574

这只是不应该使用
rand()

rand()
的另一个原因,它完全是垃圾。不要用它。任何关于它为什么不工作或者为什么不能产生好数据的问题都是因为它是垃圾。这根本不是线程安全的。使用。嗯,问题更多的是关于平行。无论如何,Ruby的内置rng是一个Mersenne twister实现,它不是“垃圾”。我不明白为什么人们会抱怨
rand
,然后推荐
Random
,因为
rand
Random::DEFAULT.rand
的别名。我是说
rand
很弱,但如果你同意的话,然后,
Random
是您的解决方案。它回避了
Kernel#rand
所面临的众多问题中的一个,而这个问题正是导致您出现问题的原因。
rand
并不弱,而且不管怎样,周期和熵等数论的东西在这里都不起作用。只要您知道风险,并且对此没有问题。这是你的电话,毕竟,我只是在这里提出一个警告,因为
rand
很容易被误认为比它更健壮(例如加密随机)。