Почему Ruby 1.9 GUI зависает, если я делаю интенсивные вычисления в отдельном потоке Ruby? - PullRequest
17 голосов
/ 30 января 2012

Ruby 1.9 должен иметь собственные потоки, а GIL должен подниматься, если некоторые потоки входят в собственный код (например, основной цикл GUI-инструментария или реализация C некоторых Ruby-библиотек).

Но если я начну следовать простому примеру кода, который отображает графический интерфейс пользователя в главном потоке и выполнит некоторую базовую математику в отдельном потоке - графический интерфейс будет зависать плохо, попробуйте изменить размер окна, чтобы увидеть его самостоятельно. Я проверил с другим инструментарием GUI, Qt (qtbindings gem) - он ведет себя точно так же. Протестировано с Ruby 1.9.3-p0 на Windows 7 и OSX 10.7

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

Тот же код в Python отлично работает без каких-либо зависаний графического интерфейса:

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

Что я делаю не так?

UPDATE

Кажется, в Ubuntu Linux нет такой проблемы, поэтому мой вопрос в основном о Windows и OSX.

UPDATE

Некоторые люди отмечают, что в OSX такой проблемы нет. Итак, я собрал пошаговое руководство для выявления и воспроизведения проблемы:

  1. Установите OSX 10.7 Lion через функцию «Восстановление». Я использовал наш тестовый отдел MB139RS / A mac mini для тестирования.
  2. Установите все обновления. Система будет выглядеть так: enter image description here
  3. Установите последнюю версию ActiveTcl с activestate.com, в моем случае это ActiveTcl 8.5.11 для OSX.
  4. Загрузите и распакуйте последнюю версию исходного кода Ruby. В моем случае это Ruby 1.9.3-p125. Скомпилируйте его и установите заменяющий системный Ruby (команды приведены ниже). Вы получите последнюю версию ruby ​​со встроенной поддержкой Tk: enter image description here
  5. Создайте файл test.rb с кодом из моего примера и запустите его. Попробуйте изменить размер окна - вы увидите ужасные лаги. Удалите поток из кода, запустите и попробуйте изменить размер окна - лаги исчезли. Я записал видео этого теста .

Команды компиляции Ruby:

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

Ответы [ 4 ]

11 голосов
/ 03 февраля 2012

Это зависание может быть вызвано кодом C привязок Ruby в Toolkit.Как вы знаете, рубиновые потоки имеют глобальную блокировку : GIL .Кажется, что смешивание между потоком Ruby bindings , потоком Tk C и потоком Pure Ruby идет не очень хорошо.вы можете попытаться добавить эти строки до того, как require 'tk':

module TkCore 
  RUN_EVENTLOOP_ON_MAIN_THREAD = true
end

Графическому инструментарию необходим основной поток для обновления графических элементов.Если ваш поток требует интенсивных вычислений, он интенсивно запрашивает блокировку и поэтому вмешивается в поток инструментария.

Вы можете избежать использования трюка sleep , если хотите.В Ruby 1.9 вы можете использовать Fiber , Revactor или EventMachine .Согласно oldmoe, Fibers кажется довольно быстрым .

Вы также можете сохранить нити Ruby, если можете использовать IO.pipe .Вот так были реализованы параллельные тесты в 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

Вызов fork инициирует два процесса.Внутри if вы находитесь в родительском процессе.Внутри else, вы в ребенке.Вызов Process.wait закрывает дочерний процесс.Например, вы можете попытаться читать от своего ребенка в основном цикле графического интерфейса пользователя и закрывать и ждать ребенка только после получения всех данных.

РЕДАКТИРОВАТЬ : Вы 'Вам понадобится win32-process , если вы решите использовать fork () под Windows.

0 голосов
/ 14 февраля 2012

Если вы серьезно относитесь к использованию нескольких потоков, вы можете рассмотреть возможность использования JRuby. Он реализует потоки Ruby с использованием потоков Java, предоставляя вам доступ к библиотекам параллелизма Java, инструментам и хорошо протестированному коду.

В большинстве случаев вы просто заменяете команду ruby ​​командой jruby.

Вот одно место для начала. https://github.com/jruby/jruby/wiki/Concurrency-in-jruby

0 голосов
/ 07 февраля 2012

В зависимости от платформы вы можете установить приоритет потоков:

require 'tk'
require 'thread'
require 'rexml/document'
t1 = Thread.new { loop { a = 1 } }
t1.priority = 0
t2 = TkRoot.new.mainloop()
t2.priority = 100
0 голосов
/ 31 января 2012

Ваш блок потока будет использовать 100% ЦП, это действительно маловероятно, что какой-либо реальный код будет так много кушать (если вы делаете очень интенсивные вычисления, вы должны рассмотреть другой язык), возможно, попробуйте добавить несколько пауз:

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.

Тем не менее, я люблю ruby, но текущее состояние библиотек графического интерфейса действительно плохое, на мой взгляд, и я избегаю использованияэто для этого.

...