ran*_*oke 6 ruby multithreading race-condition
我想知道使用MRI ruby(2.0.0)和一些全局变量来制作竞争条件是否容易,但事实证明它并不那么容易.看起来它应该在某些时候失败,但它没有,我已经运行了10分钟.这是我一直试图实现的代码:
def inc(*)
a = $x
a += 1
a *= 3000
a /= 3000
$x = a
end
THREADS = 10
COUNT = 5000
loop do
$x = 1
THREADS.times.map do Thread.new { COUNT.times(&method(:inc)) } end.each(&:join)
break puts "woo hoo!" if $x != THREADS * COUNT + 1
end
puts $x
Run Code Online (Sandbox Code Playgroud)
为什么我无法生成(或检测)预期的竞争条件,并woo hoo!在Ruby MRI 2.0.0中获得输出?
您的示例确实(几乎立即)在 1.8.7 中工作。
以下变体适用于 1.9.3+:
def inc
a = $x + 1
# Just one microsecond
sleep 0.000001
$x = a
end
THREADS = 10
COUNT = 50
loop do
$x = 1
THREADS.times.map { Thread.new { COUNT.times { inc } } }.each(&:join)
break puts "woo hoo!" if $x != THREADS * COUNT + 1
puts "No problem this time."
end
puts $x
Run Code Online (Sandbox Code Playgroud)
该sleep命令向解释器强烈暗示它可以调度另一个线程,因此这并不是一个巨大的惊喜。
请注意,如果将 替换sleep为需要同样长或更长的时间的内容,例如b = a; 500.times { b *= 100 },则在上面的代码中不会检测到竞争条件。但是进一步使用b = a; 2500.times { b *= 100 },或者COUNT从 50 增加到 500,并且可以更可靠地触发竞争条件。
Ruby 1.9.3 及以后版本(当然包括 2.0.0)中的线程调度似乎以比 1.8.7 更大的块分配 CPU 时间。在简单代码中切换线程的机会可能很低,除非涉及某种 I/O 等待。
OP 中的线程甚至有可能(每个线程仅执行几千次计算)本质上是串联发生的 - 尽管增加全局COUNT以避免这种情况仍然不会触发额外的竞争条件。
Fixnum通常,MRI Ruby在其 C 实现中发生的原子过程(例如,乘法或除法期间)期间不会在线程之间切换上下文。这意味着线程上下文切换的唯一机会是在每行代码的“中间”,其中所有方法都调用 Ruby 内部函数而无需 I/O 等待。在最初的例子中,只有 4 个这样的转瞬即逝的机会,而且似乎在计划中这对于 MRI 1.9.3+ 来说根本不是很多(事实上,请参阅下面的更新,这些机会可能已被删除)通过红宝石)
当 I/O 等待或sleep涉及时,它实际上会变得更加复杂,因为 Ruby MRI (1.9+) 将允许在多核 CPU 上进行一点真正的并行处理。虽然这不是线程竞争条件的直接原因,但更有可能导致线程竞争条件,因为 Ruby 通常会同时进行线程上下文切换以利用并行性。
当我研究这个粗略的答案时,我发现了一个有趣的链接:没有人理解 GIL(第 2 部分链接,与这个问题更相关)
更新:我怀疑解释器正在优化 Ruby 源代码中的一些潜在的线程切换点。sleep从我的代码版本开始,并设置:
COUNT = 500000
Run Code Online (Sandbox Code Playgroud)
以下变体inc似乎没有影响 的竞争条件$x:
def inc
a = $x + 1
b = 0
b += 1
$x = a
end
Run Code Online (Sandbox Code Playgroud)
然而,这些微小的变化都会触发竞争条件:
def inc
a = $x + 1
b = 0
b = b.send( :+, 1 )
$x = a
end
def inc
a = $x + 1
b = 0
b += '1'.to_i
$x = a
end
Run Code Online (Sandbox Code Playgroud)
我的解释是,Ruby 解析器已经过优化b += 1,消除了方法分派的一些开销。优化的步骤之一可能包括检查是否可能切换到等待线程。
如果是这样的话,那么问题中的代码可能永远没有机会在方法内切换线程inc,因为它内部的所有操作都可以以相同的方式进行优化。
| 归档时间: |
|
| 查看次数: |
249 次 |
| 最近记录: |