首页 > 解决方案 > Ruby 闭包、线程和作用域

问题描述

我正在尝试编写一个线程化的 Ruby 应用程序,但在理解通过块传递给线程的变量的作用域时遇到了一些麻烦。代码的简化版本如下

def threadRoutine(p1,p2)
  puts "Thread #{Thread.current.object_id} started with parameters #{p1}, #{p2}"
  Kernel.sleep 5
  puts "Thread #{Thread.current.object_id} completed"
end

start = 0
threads = []
(1..10).each do 
  finish = start + 1
  threads << Thread.new{threadRoutine(start,finish)}
  puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
  start = finish
end

threads.each do |t|
  t.join
end

输出如下

Started thread 2002 with parameters 0,1
Thread 2002 started with parameters 0, 1
Started thread 2004 with parameters 1,2
Thread 2004 started with parameters 1, 2
Started thread 2006 with parameters 2,3
Thread 2006 started with parameters 3, 3
Started thread 2008 with parameters 3,4
Thread 2008 started with parameters 4, 4
Started thread 2010 with parameters 4,5
Thread 2010 started with parameters 5, 5

Thread 2010 completed
Thread 2004 completed
Thread 2002 completed
Thread 2006 completed
Thread 2008 completed

前两个线程以与从外部例程传递的相同参数开始的线程成功启动。在此之后(线程 2006 及以后),线程中的 p1 参数接收在调用 Thread.new 之后已更新的“开始”变量的值

显然我误解了这里的工作方式,但我希望传递给 Thread.new 的块会创建一个闭包,其中包含调用该块的变量的值。这似乎并没有持续发生。请注意,如果我按照对 Thread.new 的调用进行 1 秒的睡眠,一切正常,但这感觉就像一个 hack,我想了解如何正确地做到这一点。

非常感谢任何解释和建议。

这是我正在使用的 Ruby 版本的详细信息

jruby 9.1.4.0 (2.3.1) 2016-09-01 2e1327f Java HotSpot(TM) Client VM 25.51-b03 on 1.8.0_51-b16 +jit [mswin32-x86]

标签: rubymultithreading

解决方案


查看这段代码:

(1..10).each do 
  finish = start + 1
  threads << Thread.new{threadRoutine(start,finish)}
  puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
  start = finish
end

您必须了解的是Thread.new { },它的主体不会立即评估,而是在线程可运行时评估。

Thread.new因此,存在一种竞争条件,即在评估主体之前发生此循环的多次和/或部分迭代。在这种情况下,start并且end将在此期间发生变化。这样做的原因是start在循环之外定义的。你没有适当的关闭。

为了解决这个问题,我可以提出两种可能的解决方案:

  1. 在迭代块内部定义变量,而不是在它外部。您已经在使用 执行此finish操作,只需要使用start

    10.times do |start|
      finish = start + 1
      threads << Thread.new{threadRoutine(start,finish)}
      puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
    end
    

    Ruby 具有块级闭包,除非已经在外部范围声明了同名的变量。相比:

    10.times do
      a ||= 0
      a += 1
      print a
    end
    # => 1111111111
    
    a = 0
    10.times do
      a ||= 0
      a += 1
      print a
    end
    # => 12345678910
    
  2. Thread.new { }调用移至方法。这样您就可以在变量上获得方法级别的闭包:

    def threadRoutine(p1,p2)
      Thread.new do
        puts "Thread #{Thread.current.object_id} started with parameters #{p1}, #{p2}"
        Kernel.sleep 5
        puts "Thread #{Thread.current.object_id} completed"
      end
    end
    
    # later, in the loop ...
    threads << threadRoutine(start,finish)
    

推荐阅读