2013-01-21 16 views
5

Ho scritto un semplice programma di test utilizzando i blocchi di thread. Questo programma non si comporta come previsto e l'interprete python non si lamenta.Comportamento imprevisto quando si utilizzano i blocchi di thread python e le importazioni di circulair

test1.py:

from __future__ import with_statement 
from threading import Thread, RLock 
import time 
import test2 

lock = RLock() 

class Test1(object): 
    def __init__(self): 
     print("Start Test1") 
     self.test2 = test2.Test2() 
     self.__Thread = Thread(target=self.myThread, name="thread") 
     self.__Thread.daemon = True 
     self.__Thread.start() 
     self.test1Method() 

    def test1Method(self): 
     print("start test1Method") 
     with lock: 
      print("entered test1Method") 
      time.sleep(5) 
      print("end test1Method") 

    def myThread(self): 
     self.test2.test2Method() 

if __name__ == "__main__": 
    client = Test1() 
    raw_input() 

test2.py:

from __future__ import with_statement 
import time 
import test1 

lock = test1.lock 

class Test2(object): 
    def __init__(self): 
     print("Start Test2") 

    def test2Method(self): 
     print("start test2Method") 
     with lock: 
      print("entered test2Method") 
      time.sleep(5) 
      print("end test2Method") 

Entrambi i posti letto vengono eseguiti allo stesso tempo! Non è quello che mi aspettavo quando usavo il lucchetto.

Quando test2Method viene spostato su test1.py, tutto funziona correttamente. Quando creo il blocco in test2.py e lo importa in test1.py, tutto funziona correttamente. Quando creo il blocco in un file sorgente separato e lo importa sia in test1.py che in test2.py, tutto funziona correttamente.

Probabilmente ha a che fare con le importazioni della circonvallazione.

Ma perché Python non si lamenta di questo?

risposta

1

Utilizzando print dichiarazioni prima e dopo le dichiarazioni import e la stampa id(lock) subito dopo che è stato creato rivela che ci sono in realtà due serrature in fase di creazione. Sembra che il modulo venga importato due volte e mouad nella sua risposta spiega che questo è dovuto al fatto che test1.py viene importato prima come __main__ e quindi come test1, che causa l'istanziazione del blocco due volte.

Comunque sia, utilizzare un blocco globale non è comunque una buona soluzione. Ci sono diverse soluzioni migliori, e penso che troverai che uno di loro soddisferà le tue esigenze.

  • un'istanza della serratura come una variabile di classe di Test1, e passarlo come argomento a Test2

  • un'istanza della serratura come una normale variabile di Test1 in __init__, e passarlo come argomento a Test2 .

  • istanziare il blocco nel blocco if __name__ == "__main__" e passarlo al Test1, e poi da Test1 a Test2.

  • istanziare il blocco nel blocco if __name__ == "__main__" e prima istanziare Test2 con la serratura, poi passare il Test2 esempio e la serratura Test1. (Questo è il modo più disaccoppiato per farlo, e raccomanderei di andare con questo metodo, che alleggerirà il test dell'unità, per lo meno.).

Ecco il codice per l'ultimo suggerimento:

test1.py:

class Test1(object): 
    def __init__(self, lock, test2): 
     print("Start Test1") 
     self.lock = lock 
     self.test2 = test2 
     self.__Thread = Thread(target=self.myThread, name="thread") 
     self.__Thread.daemon = True 
     self.__Thread.start() 
     self.test1Method() 

    def test1Method(self): 
     print("start test1Method") 
     with self.lock: 
      print("entered test1Method") 
      time.sleep(1) 
      print("end test1Method") 

    def myThread(self): 
     self.test2.test2Method() 

if __name__ == "__main__": 
    lock = RLock() 
    test2 = test2.Test2(lock) 
    client = Test1(lock, test2) 

test2.py:

class Test2(object): 
    def __init__(self, lock): 
     self.lock = lock 
     print("Start Test2") 

    def test2Method(self): 
     print("start test2Method") 
     with self.lock: 
      print("entered test2Method") 
      time.sleep(1) 
      print("end test2Method") 
3

In Python quando si esegue uno script python utilizzando $ python test1.py ciò accada è che il tuo test1.py sarà impor ted come __main__ invece di test1, quindi se si desidera ottenere il blocco definito nello script avviato non è necessario import test1 ma si dovrebbe import __main__ perché se si esegue il primo si creerà un altro blocco diverso da __main__.lock (test1.lock != __main__.lock) .

Così un fix per il problema (che lungi dall'essere il migliore) e vedere cosa sta succedendo è possibile modificare lo script 2 a questo:

test1.py:

from __future__ import with_statement 
from threading import Thread, RLock 
import time 

lock = RLock() 

class Test1(object): 
    def __init__(self): 
     print("Start Test1") 
     import test2 # <<<<<<<<<<<<<<<<<<<<<<<< Import is done here to be able to refer to __main__.lock. 
     self.test2 = test2.Test2() 
     self.__Thread = Thread(target=self.myThread, name="thread") 
     self.__Thread.daemon = True 
     self.__Thread.start() 
     self.test1Method() 

    def test1Method(self): 
     print("start test1Method") 
     with lock: 
      print("entered test1Method") 
      time.sleep(5) 
      print("end test1Method") 

    def myThread(self): 
     self.test2.test2Method() 

if __name__ == "__main__": 
    client = Test1() 
    raw_input() 

test2.py:

from __future__ import with_statement 
import time 
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<< test1 is changed to __main__ to get the same lock as the one used in the launched script. 
import __main__ 

lock = __main__.lock 

class Test2(object): 
    def __init__(self): 
     print("Start Test2") 

    def test2Method(self): 
     print("start test2Method") 
     with lock: 
      print("entered test2Method") 
      time.sleep(5) 
      print("end test2Method") 

HTH,

+0

Grazie per la spiegazione. Python è abbastanza nuovo per me, non ho mai incontrato questo comportamento. Sono contento di averlo chiesto, perché potrebbe verificarsi anche in altre situazioni non thread. – user1997293

+0

@ user1997293: Sì, hai ragione, questo comportamento è molto comune e felice che la mia risposta sia stata utile :) – mouad

0

Come altri hanno detto, il problema non si trova nello threading, ma nel caso particolare delle importazioni cicliche.

Perché speciale? A causa di consueto flusso di lavoro (mod1 importazioni mod2 e mod2 importazioni mod1) appare come il prossimo:

  1. Si desidera utilizzare mod1 modulo, è lo importa (import mod1)

  2. Quando Python trova, interprete lo aggiunge a sys.modules e inizia l'esecuzione di codice

  3. Quando raggiunge linea con import mod2, esso interrompe l'esecuzione di mod1 e avvia l'esecuzione di 012.

  4. Quando interprete raggiunge import mod1 all'interno mod2, è non si carico mod1 perché è stato già aggiunto al sys.modules

  5. Dopo di che (a meno che qualche codice nella mod2 accede a una risorsa non inizializzato da mod1) finiture interprete esecuzione di mod2 e mod1.

ma nel tuo caso al punto 4. Python esegue test1 ancora una volta perché non c'è test1 in sys.modules! Il motivo è che non l'hai importato in primo luogo, ma eseguilo da una riga di comando.

Quindi, non utilizzare le importazioni cicliche - come si vede è un vero casino.

+0

Infatti, di solito evito le importazioni cicliche. Questa volta si trattava di un piccolo programma di test inizialmente inteso a verificare se il blocco si comportasse come un singleton, cosa che non ha funzionato. Questo primo test non ha avuto importazioni cicliche. Poi ho cambiato il programma un po 'per qualcosa che avrebbe funzionato, ma purtroppo. – user1997293

Problemi correlati