如果我在单独的Ruby线程中进行任何密集计算,为什么Ruby 1.9 GUI会挂起?

时间:2012-01-30 12:30:04

标签: ruby multithreading ruby-1.9

Ruby 1.9应该具有本机线程,如果某些线程进入本机代码(如GUI工具包主循环或某些Ruby lib的C实现),GIL应该会解除。

但是,如果我开始关注在主线程中显示GUI的简单代码示例并在单独的线程中执行一些基本的数学运算 - GUI将严重挂起,尝试调整窗口大小以便自己查看。我已经检查过不同的GUI工具包,Qt(qtbindings gem) - 它的行为完全相同。在Windows 7和OSX 10.7上使用Ruby 1.9.3-p0进行测试

require 'tk'
require 'thread'
Thread.new { loop { a = 1 } }
TkRoot.new.mainloop()

Python中的相同代码在没有任何GUI挂起的情况下工作正常:

from Tkinter import *
from threading import *
class WorkThread( Thread ) :
  def run( self ) :
    while True :
      a = 1
WorkThread().start()
Tk().mainloop()

我做错了什么?

更新

似乎Ubuntu linux上没有这样的问题,所以我的问题主要是关于Windows和OSX。

更新

有些人指出OSX上没有这样的问题。因此,我逐步制定了分离和重现问题的分步指南:

  1. 通过“恢复”功能安装OSX 10.7 Lion。我用我们的测试部门MB139RS / A mac mini进行测试。
  2. 安装所有更新。系统将如下所示: enter image description here
  3. 从activestate.com安装最新的ActiveTcl,在我的情况下,它是针对OSX的ActiveTcl 8.5.11。
  4. 下载并解压最新的Ruby源代码。就我而言,它是Ruby 1.9.3-p125。编译它并安装替换系统Ruby(下面的命令)。您将获得内置Tk支持的最新ruby: enter image description here
  5. 使用我的示例中的代码创建一个test.rb文件并运行它。尝试调整窗口大小 - 你会看到可怕的滞后。从代码中删除线程,启动并尝试调整窗口大小 - 滞后消失。我录了video of this test
  6. Ruby编译命令:

    ./configure --with-arch=x86_64,i386 --enable-pthread --enable-shared --with-gcc=clang --prefix=/usr
    make
    sudo make install
    

4 个答案:

答案 0 :(得分:11)

这种挂起可能是由Toolkit中的Ruby绑定的C代码引起的。如您所知,ruby线程有一个global lockGIL。似乎在Ruby bindings' C thread,Tk C线程和Pure Ruby线程之间混合并不顺利。

对于类似的情况有documented workaround,您可以尝试在require 'tk'之前添加这些行:

module TkCore 
  RUN_EVENTLOOP_ON_MAIN_THREAD = true
end

图形工具包需要一个主线程才能刷新图形元素。如果您的线程处于密集计算中,那么您的线程会严重请求锁定,因此它会干扰工具箱的线程。

如果需要,可以避免使用 sleep 技巧。在Ruby 1.9中,您可以使用FiberRevactorEventMachine。根据oldmoe,Fibers seems to be quite fast

如果可以使用IO.pipe,还可以保留Ruby线程。这就是在ruby 1.9.3中并行测试were implemented的方法。这似乎是解决Ruby线程和GIL限制的好方法。

文档显示了一个示例用法:

rd, wr = IO.pipe

if fork 
  wr.close
  puts "Parent got: <#{rd.read}>"
  rd.close
  Process.wait
else 
  rd.close
  puts "Sending message to parent"
  wr.write "Hi Dad"
  wr.close
end

fork调用会启动两个进程。在if内,您处于父进程中。在else里面,你就是孩子。对Process.wait的调用将关闭子进程。 例如,您可以尝试从您的主要gui循环中读取您的孩子,并且只能关闭&amp;收到所有数据后等待孩子。

编辑:如果您选择在Windows下使用fork(),则需要win32-process

答案 1 :(得分:0)

你的线程块将使用100%cpu,这真的不太可能任何真正的代码会吃那么多(如果你正在进行真正密集的计算,你应该考虑另一种语言),也许尝试添加一些暂停:

require 'tk'
require 'thread'
require 'rexml/document'
Thread.new { loop { sleep 0.1; a = 1 } }
TkRoot.new.mainloop()

你的代码在Mac OS X 10.7上运行良好,只需1.9.3 btw。

这就像我喜欢红宝石一样说但是目前的gui库状态在我看来真的很糟糕,我不会用它。

答案 2 :(得分:0)

根据平台的不同,您可以设置线程的优先级:

require 'tk'
require 'thread'
require 'rexml/document'
t1 = Thread.new { loop { a = 1 } }
t1.priority = 0
t2 = TkRoot.new.mainloop()
t2.priority = 100

答案 3 :(得分:0)

如果您认真使用多个线程,可能需要考虑使用JRuby。它使用Java线程实现Ruby Threads,使您可以访问Java并发库,工具和经过良好测试的代码。

在大多数情况下,您只需使用jruby命令替换ruby命令。

这是一个开始的地方。 https://github.com/jruby/jruby/wiki/Concurrency-in-jruby