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上没有这样的问题。因此,我逐步制定了分离和重现问题的分步指南:
test.rb
文件并运行它。尝试调整窗口大小 - 你会看到可怕的滞后。从代码中删除线程,启动并尝试调整窗口大小 - 滞后消失。我录了video of this test。Ruby编译命令:
./configure --with-arch=x86_64,i386 --enable-pthread --enable-shared --with-gcc=clang --prefix=/usr
make
sudo make install
答案 0 :(得分:11)
这种挂起可能是由Toolkit中的Ruby绑定的C代码引起的。如您所知,ruby线程有一个global lock:GIL。似乎在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中,您可以使用Fiber,Revactor或EventMachine。根据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