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

gri*_*yvp 17 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()
Run Code Online (Sandbox Code Playgroud)

Python中的相同代码工作正常,没有任何GUI挂起:

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

我做错了什么?

UPDATE

看来在Ubuntu linux上没有这样的问题,所以我的问题主要是关于Windows和OSX.

UPDATE

有人指出OSX上没有这样的问题.所以我汇编了一个分步和重现问题的分步指南:

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

Ruby编译命令:

./configure --with-arch=x86_64,i386 --enable-pthread --enable-shared --with-gcc=clang --prefix=/usr
make
sudo make install
Run Code Online (Sandbox Code Playgroud)

Cor*_*ren 11

这种挂起可能是由Toolkit中的Ruby绑定的C代码引起的.如您所知,ruby线程具有全局锁定:GIL.似乎Ruby绑定的C线程,Tk C线程和Pure Ruby线程之间的混合并不顺利.

针对类似案例有一个记录的解决方法,您可以尝试在之前添加这些行require 'tk':

module TkCore 
  RUN_EVENTLOOP_ON_MAIN_THREAD = true
end
Run Code Online (Sandbox Code Playgroud)

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

如果你愿意,你可以避免使用睡眠技巧.在Ruby 1.9中,您可以使用Fiber,RevactorEventMachine.据oldmoe说,纤维似乎很快.

如果可以使用IO.pipe,也可以保留Ruby线程.这就是在ruby 1.9.3 中实现并行测试的方式.这似乎是解决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
Run Code Online (Sandbox Code Playgroud)

fork呼叫启动两个进程.在里面if,你在父进程中.在里面else,你是在孩子.Process.wait关闭子进程的调用.例如,您可以尝试在主gui循环中读取您的孩子,并且只有在您收到所有数据后才关闭并等待孩子.

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

  • @EyeofHell不是解决方案,而是一种解决方法:REXML非常慢.如果您要切换到Nokogiri进行XML处理,那么您的滞后会消失...... (2认同)