首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Ruby中的多线程(MRI)

Ruby中的多线程(MRI)
EN

Stack Overflow用户
提问于 2014-07-25 18:32:41
回答 3查看 2K关注 0票数 1

根据Ruby (MRI)中的GIL实现,下面的代码肯定会因为多次打印一条消息而失败。但它不会,它总是只打印一次:

代码语言:javascript
复制
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)

怎么会这样?

代码语言:javascript
复制
$ ruby --version
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
EN

回答 3

Stack Overflow用户

发布于 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很可能调度线程的粒度更细。

在任何情况下,您都不应该依赖任何附带的线程调度行为。这不是任何有文档记录的行为和事情的一部分,随着的成熟,它将在不同的系统上进行更改。在使用线程时,应始终确保安全地使用共享数据结构。

票数 2
EN

Stack Overflow用户

发布于 2015-05-12 10:44:22

我使用的是Ruby版本的ruby 2.1.5p273,我想稍有不同的Ruby版本应该会产生类似的结果。

每次运行程序时,我都会得到不同的结果。

我试着启用了一个内核,并启用了前几个内核。我看不出有什么不同。正如您所期望的,它不是线程安全的。

否则,我能想到的唯一答案就是你的程序太快/太轻,所以解释器不会经常考虑线程切换。

在这种情况下,我只有一个建议。这是一个技巧,你可以用来给解释器一个提示,也许她可以切换线程。您可以使用sleep函数。

在您的示例中,我会将其放在race condition之前

代码语言:javascript
复制
  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

票数 0
EN

Stack Overflow用户

发布于 2016-04-09 03:20:32

正如其他人所提到的,GIL的行为没有文档记录,并且完全依赖于实现。您不应该依赖于对它的调度行为的任何期望。

然而,一个更详细(也更一般)的答案是,调度器在线程之间切换执行,以确保没有单个线程阻塞进程。这种切换称为上下文切换,或者更具体地说,称为线程切换。

当上下文切换发生时,当前线程的执行将暂停,而另一个线程的执行将恢复。如果它是一个正在“恢复”的全新线程,则意味着新线程的执行从头开始。

在您的程序中,每个新线程都以

代码语言:javascript
复制
s.shorn?

因为它对unless s.shorn?求值。此时,@shorn == falses.shorn?的计算结果为false。然后线程就会运行:

代码语言:javascript
复制
s.shorn!

#shorn!中运行的第一个命令是:

代码语言:javascript
复制
puts "shearing..."

接下来会发生什么取决于线程调度器:

  1. 如果调度程序决定让当前线程继续执行,则执行的下一个命令是@shorn = true。然后线程结束,调度程序启动下一个线程,unless s.shorn?计算结果为true,然后线程停止。此行为在循环中重复,直到没有其他线程为止。
  2. 如果调度程序决定切换到另一个线程,则它将在@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线程安全机制。然而,对于像这样的小示例,一个简单的互斥就可以了:

代码语言:javascript
复制
semaphore = Mutex.new

s = Sheep.new
55.times.map {
  Thread.new {
    semaphore.synchronize do
      s.shorn! unless s.shorn?
    end
  }
}.each(&:join)
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24953702

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档