根据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 { s.shorn! unless s.shorn? }
end.each(&:join)怎么会这样?
$ ruby --version
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]发布于 2014-07-25 18:47:09
这有点依赖于你使用的确切的ruby版本(在调度线程的方式上有所不同)。在我的系统上,这有点取决于整体系统负载和终端的速度,但在Ruby 2.0.00p481上,我得到的输出在1到55行之间,在Ruby 1.8.7上,我始终只得到一行。
这里应该注意的是,Ruby 2.0和更高版本使用实际的OS线程(尽管仍然使用GIL),而Ruby 1.8使用内部绿色线程和它自己的调度。老版本的ruby很可能调度线程的粒度更细。
在任何情况下,您都不应该依赖任何附带的线程调度行为。这不是任何有文档记录的行为和事情的一部分,随着的成熟,它将在不同的系统上进行更改。在使用线程时,应始终确保安全地使用共享数据结构。
发布于 2015-05-12 10:44:22
我使用的是Ruby版本的ruby 2.1.5p273,我想稍有不同的Ruby版本应该会产生类似的结果。
每次运行程序时,我都会得到不同的结果。
我试着启用了一个内核,并启用了前几个内核。我看不出有什么不同。正如您所期望的,它不是线程安全的。
否则,我能想到的唯一答案就是你的程序太快/太轻,所以解释器不会经常考虑线程切换。
在这种情况下,我只有一个建议。这是一个技巧,你可以用来给解释器一个提示,也许她可以切换线程。您可以使用sleep函数。
在您的示例中,我会将其放在race condition之前
def shorn!
sleep 0.0001
puts "shearing..."
@shorn = true
end如果你想了解更多关于GIL的信息,我可以推荐Jesse Storimer的Nobody understands the GIL
如果您想了解更多关于Ruby和并发的内容,我可以推荐Dotan Nahum的Pragmatic Concurrency with Ruby
我建议的技巧是提到in this answer
发布于 2016-04-09 03:20:32
正如其他人所提到的,GIL的行为没有文档记录,并且完全依赖于实现。您不应该依赖于对它的调度行为的任何期望。
然而,一个更详细(也更一般)的答案是,调度器在线程之间切换执行,以确保没有单个线程阻塞进程。这种切换称为上下文切换,或者更具体地说,称为线程切换。
当上下文切换发生时,当前线程的执行将暂停,而另一个线程的执行将恢复。如果它是一个正在“恢复”的全新线程,则意味着新线程的执行从头开始。
在您的程序中,每个新线程都以
s.shorn?因为它对unless s.shorn?求值。此时,@shorn == false和s.shorn?的计算结果为false。然后线程就会运行:
s.shorn!在#shorn!中运行的第一个命令是:
puts "shearing..."接下来会发生什么取决于线程调度器:
@shorn = true。然后线程结束,调度程序启动下一个线程,unless s.shorn?计算结果为true,然后线程停止。此行为在循环中重复,直到没有其他线程为止。@shorn = true之前暂停执行,并从头开始运行与以前相同的代码。这意味着在新线程启动时执行@shorn == false,因此puts "shearing..."将再次执行。如您所见,这完全取决于调度器何时决定执行上下文切换。
但是GIL呢?
GIL是MRI Ruby中一个被严重误解的部分。有很多资源可以解释GIL是如何工作的,但在这种情况下,您应该知道的最重要的事情是,GIL不能保证每个线程都会按顺序运行。
相反,GIL只是保证大多数用C实现的核心Ruby方法(例如,Array#<<)在它们完成之前不会被上下文切换中断。在puts "shearing..."的例子中,我没有看过puts的代码,但是GIL可能保证在当前运行的线程执行完puts之前不会运行其他线程。
至于为什么在MI1.8.7下运行代码时,它只显示shearing...一次,这与绿色线程和原生线程没有任何关系。更好的答案是,这是一个巧合。更准确的答案是,在您的情况下,由于某种原因,调度程序决定在运行@shorn = true之后中断第一线程。这种行为可能是由于绿色线程造成的,因为您的本机调度器可能比Ruby的调度器更频繁地中断(因此在下面的答案之一中提出了“更细粒度”的建议),但这不一定是真的。这也可能是一次偶然的机会。
Ruby中的多线程很容易搞砸。因此,Matz建议坚持使用forking进程,这是内存低效的,但消除了管理线程的负担。对于较大的项目,另一种方法是使用像Celluloid这样的库,它抽象了Ruby线程安全机制。然而,对于像这样的小示例,一个简单的互斥就可以了:
semaphore = Mutex.new
s = Sheep.new
55.times.map {
Thread.new {
semaphore.synchronize do
s.shorn! unless s.shorn?
end
}
}.each(&:join)https://stackoverflow.com/questions/24953702
复制相似问题