使用srand和rand的并行gem令人惊讶的输出
我正在使用红宝石2.4.1 +并行1.11.2。 我在irb中运行以下内容:
require 'parallel'
srand(1)
Parallel.map([0, 1], in_processes: 2) { |i| puts "in process #{i}; rand => #{rand}" }
我的理解是,当指定in_processes
时, 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,所以似乎没有进程获得我设置的随机数种子。 我可以通过在循环中设置随机数种子来获得我想要的行为,但在我这样做之前,我试图理解为什么它不能将种子放在循环之外。
我试图理解这种情况。 这种行为在某种程度上是随机数生成器所特有的,所以我不一定有与其他对象相同的问题(即预期的共享初始状态并且没有得到它)? 或者是Parallel与真正的fork
系统调用没有相同的效果?
有关in_processes
并行的文档使我相信它的行为像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
编辑:进一步的调查似乎表明,选项isolation: 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]
但isolation: true
似乎没有rand
预期的效果。 仍然不明白那里发生了什么。
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()
另一个原因。
上一篇: Surprising output using Parallel gem with srand and rand