Подтвердить что ты не робот

Почему Ruby 1.9 GUI зависает, если я делаю какие-либо интенсивные вычисления в отдельной рубиновой нити?

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

Но если я начну следовать простейшему образцу кода, который отображает графический интерфейс в основном потоке и выполняет некоторую базовую математику в отдельном потоке - GUI будет плохо болтаться, попробуйте изменить размер окна, чтобы увидеть его самостоятельно. Я проверил с помощью другого инструментария 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 отлично работает без зависания GUI:

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

Что я делаю неправильно?

UPDATE

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

UPDATE

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

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

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

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

Ответ 1

Это зависание может быть вызвано C-кодом привязок Ruby в Toolkit. Как вы знаете, рубиновые потоки имеют глобальный замок : GIL. Кажется, что смешивание между потоком потока Ruby C, нить 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 завершает дочерний процесс. Например, вы можете попытаться прочитать ваш ребенок в своем основном цикле gui и только закрыть и ждать ребенка, когда вы получили все данные.

EDIT: вам понадобится win32-process, если вы решите использовать fork() под Windows.

Ответ 2

В вашем потоковом блоке будет использоваться 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, но текущее состояние GUI-библиотек действительно плохо по моему мнению, и я не использую его для этого.

Ответ 3

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

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

Ответ 4

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

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

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