2012-01-30 16 views
17

Si suppone che Ruby 1.9 disponga di thread nativi e GIL dovrebbe sollevare se alcuni thread immettono codice nativo (come il loop principale del toolkit GUI o implementazione di C di una lib di Ruby).Perché la GUI di Ruby 1.9 si blocca se eseguo calcoli intensivi in ​​thread Ruby separati?

Ma se inizio a seguire un semplice esempio di codice che mostra la GUI nel thread principale e faccio alcuni calcoli matematici di base in thread separati - la GUI si bloccherà, provate a ridimensionare la finestra per vederla da soli. Ho controllato con diversi GUI toolkit, Qt (qtbindings gem) - si comporta esattamente allo stesso modo. Testato con Ruby 1.9.3-p0 su Windows 7 e OSX 10,7

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

stesso codice in Python funziona bene senza alcuna interfaccia grafica si blocca:

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

quello che sto facendo di sbagliato?

UPDATE

Sembra dove è nessun problema su Ubuntu Linux, quindi la mia domanda è principalmente su Windows e OSX.

UPDATE

Alcune persone osserva che, mentre non è un problema del genere su OSX. Così ho preparato una guida passo-passo per isolare e riprodurre un problema:

  1. Installare OSX 10.7 Lion tramite la funzione "Ripristino". Ho usato il nostro reparto test MB139RS/A mac mini per il test.
  2. Installa tutti gli aggiornamenti. Il sistema sarà simile a questa: enter image description here
  3. installare l'ultima ActiveTcl da activestate.com, nel mio caso è ActiveTcl 8.5.11 per OSX.
  4. Scaricare e decomprimere l'ultimo codice sorgente di Ruby. Nel mio caso è Ruby 1.9.3-p125. Compilalo e installa il sistema di sostituzione Ruby (comandi di seguito). Si finirà con l'ultimo rubino con supporto Tk integrato: enter image description here
  5. Creare un file test.rb con codice dal mio esempio ed eseguirlo. Prova a ridimensionare una finestra: vedrai terribili ritardi. Rimuovi il thread dal codice, avvia e prova a ridimensionare una finestra: i ritardi sono spariti. Ho registrato un video of this test.
comandi

Rubino compilazione:

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

Non sicuro, ma Ruby 1.9 ha ancora un GIL (Global Interpreter Lock)? Questo spiegherebbe completamente il tuo problema ... – Romain

+0

@Romain In che modo GIL spiega il mio problema? Python ha lo stesso GIL e nessun problema. – grigoryvp

+0

GIL significa che solo un singolo thread può eseguire il codice ruby ​​in una volta, quindi se il calcolo in background può utilizzarlo, il tuo codice UI non può. – Romain

risposta

12

Questo blocco può essere causato dal codice C dei binding Ruby in Toolkit. Come sapete, i thread rubini hanno uno global lock: il GIL.Sembra che il mix tra Ruby bindings' C thread, thread Tk C e thread Pure Ruby non stia andando bene.

C'è un documented workaround per un caso simile, si può provare ad aggiungere tali righe prima require 'tk':

module TkCore 
    RUN_EVENTLOOP_ON_MAIN_THREAD = true 
end 

toolkit grafico ha bisogno di un filo conduttore al fine di aggiornare gli elementi grafici. Se il tuo thread è in un calcolo intensivo, il tuo thread richiede molto il lock e quindi interferisce con il thread del toolkit.

È possibile evitare l'uso del trucco sleep se lo si desidera. In Ruby 1.9, è possibile utilizzare Fiber, Revactor o EventMachine. Secondo oldmoe, fibre seems to be quite fast.

È anche possibile mantenere i thread Ruby se è possibile utilizzare IO.pipe. Ecco come i test paralleli were implemented in rubino 1.9.3. Sembra essere un buon modo per aggirare i thread in Ruby e le limitazioni GIL.

Documentation mostra un esempio dell'uso:

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 chiamata fork avvia due processi. All'interno di if, ci si trova nel processo principale. Dentro else, sei nel bambino. La chiamata a Process.wait chiude il processo figlio. Puoi, ad esempio, provare a leggere da tuo figlio nel tuo ciclo di gui principale, e solo chiudere & attendere il bambino quando hai ricevuto tutti i dati.

EDIT: Avrete bisogno win32-process se si sceglie di utilizzare fork() sotto Windows.

+0

Sfortunatamente, aggiungendo questa riga prima di 'require 'tk'' non cambia assolutamente nulla su Windows e OSX. GUI si blocca ancora male. Hai controllato tu stesso questa soluzione? Forse puoi incollare l'intero codice qui, forse mi manca qualcosa? – grigoryvp

+0

No, non ho provato questa soluzione alternativa. Hai provato con jruby? – Coren

+0

E forse il problema è con la chiamata 'loop'. Hai provato con un 'while true', come hai fatto con Python? – Coren

0

vostro blocco di filettatura utilizzerà 100% della CPU, questo è davvero improbabile che tutto il codice vero e proprio si mangia più di tanto (se si sta facendo i calcoli molto intensivi si dovrebbe prendere in considerazione un'altra lingua), magari provare l'aggiunta di alcune pause:

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

il tuo codice funziona bene per me su Mac OS X 10.7 con 1.9.3 btw.

Detto questo tanto quanto amo rubino ma lo stato attuale delle librerie gui è davvero pessimo secondo me e io evito di usarlo per quello.

+0

Nel codice Python è anche carico al 100% della CPU, ma non si blocca la GUI. Il multithreading consiste nell'eseguire più thread, indipendentemente dall'utilizzo della CPU per thread. Ad esempio, se avvii TWO thread ruby ​​con lo stesso loop infinito, entrambi funzioneranno perfettamente in parallelo, usando il 50% di CPU ciascuno. Quindi, se la GUI non funziona in questo modo? – grigoryvp

+0

Informazioni su OSX 10.7: basta usare il ridimensionamento invece del movimento della finestra, OSX sposta la finestra senza ritardi anche se la GUI non risponde, è la caratteristica del sistema operativo. – grigoryvp

+0

ok con ridimensionamento ho ottenuto lag troppo :) – Schmurfy

0

A seconda piattaforma è possibile impostare la priorità di thread:

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

Nessun effetto su Windows :(. Sembra questo il comportamento non è correlato alla priorità del thread/utilizzo della CPU – grigoryvp

0

Se siete seriamente su come utilizzare più thread, si potrebbe voler considerare l'utilizzo di JRuby. Implementa thread in Ruby usando thread Java, dando accesso alle librerie di concorrenza Java, strumenti e codice ben collaudato.

Per la maggior parte, basta sostituire il comando ruby ​​con il comando jruby.

Ecco un punto di partenza. https://github.com/jruby/jruby/wiki/Concurrency-in-jruby

+0

So cosa è JRuby Sono solo curioso cosa sta succedendo - non riesco a capire il comportamento dell'MRI osservo O_O. Non può essere così se GIL è rilasciato Non può essere così se GIL non viene rilasciato. – grigoryvp