ruby - 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]
解决方案
查看这段代码:
(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
在循环之外定义的。你没有适当的关闭。
为了解决这个问题,我可以提出两种可能的解决方案:
在迭代块内部定义变量,而不是在它外部。您已经在使用 执行此
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
将
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)
推荐阅读
- git - Git 使用 rebase 重写历史
- python - Python Selenium 访问内部元素
- java - Removing lock on non-graceful application shutdown
- python - How to make my bullets fire when I press my key?
- php - Laravel - 测试。如何修复错误 Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- r - R - 不能从
我有一个从普通数据帧生成的数据帧向量列表,如下所示。基因表达包含标识每个观察的细胞系、基因、染色体区域和表达水平的列——多个基因可能属于特定的染色体区域,我想将一个染色体区域的表达与其他所有基因进行比较。它看起来像这样:
gene region cell_line
- google-mlkit - 姿态检测操作模式和c++API
- java - 如何将oracle数据库18cXE连接到spring boot
- osgearth - 找不到 osgearth 缓存实用程序
- php - 从种子表 Laravel 中检索类别名称