ruby multithreading ruby-1.9

¿Por qué Ruby 1.9 GUI se cuelga si hago algún cálculo intensivo en un hilo de Ruby separado?



multithreading ruby-1.9 (4)

Se supone que Ruby 1.9 tiene subprocesos nativos, y se supone que GIL se levanta si algunos subprocesos entran en código nativo (como el bucle principal del kit de herramientas GUI o la implementación en C de algunas librerías de Ruby).

Pero si comienzo a seguir un ejemplo de código simple que muestra la GUI en el hilo principal y hago algunos cálculos básicos en un hilo separado: la GUI se quedará mal, intente cambiar el tamaño de la ventana para verlo usted mismo. He comprobado con diferentes herramientas de GUI, Qt (qtbindings gema) - se comporta exactamente igual. Probado con Ruby 1.9.3-p0 en Windows 7 y OSX 10.7

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

El mismo código en Python funciona bien sin ningún bloqueo de GUI:

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

¿Qué estoy haciendo mal?

ACTUALIZAR

Parece que no hay tal problema en Ubuntu Linux, así que mi pregunta es principalmente sobre Windows y OSX.

ACTUALIZAR

Algunas personas señalan que no hay tal problema en OSX. Así que armé una guía paso a paso para aislar y reproducir un problema:

  1. Instale OSX 10.7 Lion a través de la función "Recuperación". Utilicé nuestro departamento de pruebas MB139RS / A mac mini para pruebas.
  2. Instalar todas las actualizaciones. El sistema se verá así:
  3. Instalar el último ActiveTcl de activestate.com, en mi caso es ActiveTcl 8.5.11 para OSX.
  4. Descargar y descomprimir el último código fuente de Ruby. En mi caso es Ruby 1.9.3-p125. Compílelo e instale el sistema de reemplazo Ruby (los comandos a continuación). Terminarás con el último rubí con soporte Tk incorporado:
  5. Cree un archivo test.rb con código de mi ejemplo y ejecútelo. Intenta redimensionar una ventana, verás terribles retrasos. Elimine el hilo del código, inicie e intente cambiar el tamaño de una ventana: los retrasos desaparecen. Grabé un video de esta prueba .

Comandos de compilación de ruby:

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


Dependiendo de la plataforma, puede establecer la prioridad de los hilos:

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


El bloque de subprocesos usará el 100% de la CPU, esto es muy improbable que algún código real comiera tanto (si está haciendo cálculos muy intensos, debería considerar otro idioma), tal vez intente agregar algunas pausas:

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

Su código funciona bien para mí en Mac OS X 10.7 con 1.9.3 btw.

Dicho eso, me encanta Ruby, pero el estado actual de las bibliotecas GUI es muy malo en mi opinión y evito usarlo para eso.


Este bloqueo puede ser causado por el código C de los enlaces de Ruby en Toolkit. Como ustedes saben, los hilos de rubí tienen un bloqueo global : el GIL . Parece que la mezcla entre el hilo C de Ruby bindings, el hilo Tk C y el hilo Pure Ruby no va bien.

Hay una solución documentada para un caso similar, puede intentar agregar esas líneas antes de require ''tk'' :

module TkCore RUN_EVENTLOOP_ON_MAIN_THREAD = true end

El kit de herramientas gráficas necesita un hilo principal para actualizar los elementos gráficos. Si su hilo está en un cálculo intensivo, su hilo está solicitando mucho el bloqueo y por lo tanto está interfiriendo con el hilo del kit de herramientas.

Puedes evitar el uso del truco del sueño si quieres. En Ruby 1.9, puedes usar Fiber , Revactor o EventMachine . Según Oldmoe, las fibras parecen ser bastante rápidas .

También puedes mantener los hilos de Ruby si puedes usar IO.pipe . Así es como se implementaron las pruebas paralelas en ruby ​​1.9.3. Parece ser una buena manera de solucionar los hilos de Ruby y las limitaciones de GIL.

La documentación muestra un uso de muestra:

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

La llamada al fork inicia dos procesos. Dentro de if , estas en el proceso padre. En el else , estás en el niño. La llamada a Process.wait cierra el proceso hijo. Por ejemplo, puede intentar leer de su hijo en su gui loop principal, y solo cerrar y esperar a que el niño haya recibido todos los datos.

EDITAR : Necesitará win32-process si elige usar fork () en Windows.


Si es serio acerca del uso de múltiples subprocesos, puede considerar usar JRuby. Implementa Ruby Threads utilizando subprocesos de Java, lo que le brinda acceso a las bibliotecas, herramientas y código bien probado de concurrencia de Java.

En su mayor parte, simplemente reemplaza el comando ruby ​​con el comando jruby.

Aquí hay un lugar para comenzar. https://github.com/jruby/jruby/wiki/Concurrency-in-jruby