Ruby中的多线程(MRI)
根据Ruby(MRI)中的GIL实现,以下代码必须通过多次打印消息而失败。但事实并非如此,它总是打印一次:Ruby中的多线程(MRI),ruby,multithreading,Ruby,Multithreading,根据Ruby(MRI)中的GIL实现,以下代码必须通过多次打印消息而失败。但事实并非如此,它总是打印一次: class Sheep def initialize @shorn = false end def shorn? @shorn end def shorn! puts "shearing..." @shorn = true end end s = Sheep.new 55.times.map do Thread.new {
class Sheep
def initialize
@shorn = false
end
def shorn?
@shorn
end
def shorn!
puts "shearing..."
@shorn = true
end
end
s = Sheep.new
55.times.map do
Thread.new { s.shorn! unless s.shorn? }
end.each(&:join)
为什么
$ ruby --version
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
这在一定程度上取决于您使用的确切ruby版本(它们调度线程的方式不同)。在我的系统上,这有点取决于整个系统负载和终端的速度,但在Ruby 2.0.00p481上,我得到1到55行输出,在Ruby 1.8.7上,我始终只得到一行 这里应该注意的是,Ruby 2.0和更高版本使用实际的OS线程(尽管仍然使用GIL),而Ruby 1.8使用内部绿色线程,并有自己的调度。旧的ruby版本很可能会对线程进行更细粒度的调度
在任何情况下,您都不应该依赖任何附带的线程调度行为。这不是任何记录的行为的一部分,随着Ruby的成熟,不同系统上的情况将发生变化。在使用线程时,您应该始终确保安全地使用共享数据结构。我使用Ruby版本
Ruby 2.1.5p273
,我认为您稍微不同的Ruby版本应该会产生类似的结果
我每次运行程序都会得到不同的结果
我尝试启用单核和前核。我看不出有什么不同。它不是线程安全的,正如您所期望的那样
否则,我能想到的唯一答案是,您的程序太快/轻量级,因此解释器不会经常考虑线程切换
在这种情况下,我只有一个建议。这是一个技巧,你可以给解释器一个提示,也许她可以切换线程。您可以使用sleep
功能
在您的示例中,我会将其放在竞赛条件之前:
def shorn!
sleep 0.0001
puts "shearing..."
@shorn = true
end
如果你想了解更多关于GIL的信息,我可以推荐杰西·斯托莱默
如果您想了解更多关于Ruby和并发性的信息,我可以推荐Dotan Nahum
我建议的技巧被提到了正如其他人提到的,GIL的行为没有文档记录,完全依赖于实现。您不应该依赖于对其调度行为的任何期望
然而,更详细(也是更一般)的答案是,调度器在线程之间切换执行,以确保没有单个线程阻止进程。这个开关称为上下文开关,或者更具体地说是线程开关
当上下文切换发生时,当前线程的执行将暂停,另一个线程的执行将恢复。如果是一个全新的线程正在“恢复”,那么这意味着新线程的执行从头开始
在您的程序中,每个新线程都以
s.shorn?
除非s.shorn?
,否则其计算结果为。此时,@shorn==false
和s.shorn?
的计算结果为false。然后线程运行:
s.shorn!
#shorn中的第一个命令运行的代码>是:
将“剪切…”
接下来会发生什么取决于线程调度程序:
如果调度程序决定让当前线程继续执行,那么执行的下一个命令是@shorn=true
。然后线程结束,调度程序启动下一个线程,,除非s.shorn?
的计算结果为true,线程停止。此行为在循环中重复,直到不再有线程
如果调度程序决定切换到另一个线程,那么它将在@shorn=true
之前暂停执行,并从一开始就开始运行与以前相同的代码。这意味着新线程启动时@shorn==false
,因此将再次执行“剪切…”
如您所见,这完全取决于调度器何时决定执行上下文切换
但是吉尔呢?
GIL是MRI Ruby中被严重误解的一部分。有很多资源可以解释GIL是如何工作的,但是在本例中,您应该知道的最重要的一点是GIL不能保证每个线程都按顺序运行
相反,GIL仅仅保证用C实现的大多数核心Ruby方法(例如,Array#Ruby 2.0使用操作系统线程的证据在哪里?我使用Ruby 2.0,但总是有一条打印出来的消息。即使它是本机操作系统线程,MRI Ruby也不能同时执行多个线程,是吗?确切地说,Ruby 2.0使用操作系统线程,但它仍然有它的GIL()这确保了一次只能运行一个线程。请注意,还有其他Ruby实现,最突出的是JRuby和Rubinius,它们提供了真正的并行性,即在没有GIL的情况下(在多CPU机箱上)并行运行多个线程的能力。至于“证明”Ruby 1.9及更新版本使用本机线程,请参阅Yehuda Katz的博客文章。