2013-01-21 18 views
5

Napisałem prosty program testowy, używając blokad wątków. Ten program nie działa zgodnie z oczekiwaniami, a interpreter python nie narzeka.Nieoczekiwane zachowanie podczas używania blokad wątków python i importu 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") 

Oba śpi są wykonywane w tym samym czasie! Nie to, czego się spodziewałem podczas korzystania z zamka.

Kiedy test2Method zostanie przeniesiony do test1.py, wszystko działa poprawnie. Kiedy tworzę blokadę w test2.py i importuję ją w test1.py wszystko działa dobrze. Kiedy tworzę blokadę w osobnym pliku źródłowym i importuję ją zarówno w test1.py jak i test2.py wszystko działa poprawnie.

Prawdopodobnie ma to związek z importem Circulair.

Ale dlaczego Python na to nie narzeka?

Odpowiedz

1

Korzystanie print oświadczenia przed i po sprawozdaniu import i drukowanie id(lock) zaraz po to został stworzony wynika, że ​​istnieją w rzeczywistości dwa zamki tworzony. Wygląda na to, że moduł jest importowany dwukrotnie, a mouad wyjaśnia w swojej odpowiedzi, że jest to spowodowane tym, że test1.py jest najpierw importowany jako __main__, a następnie jako test1, co powoduje, że blokada jest tworzona dwukrotnie.

Niezależnie od tego, używanie blokady globalnej nie jest dobrym rozwiązaniem. Istnieje kilka lepszych rozwiązań i myślę, że jeden z nich będzie pasował do twoich potrzeb.

  • instancję zamek jako klasa zmiennej Test1 i przekazać je jako argument Test2

  • instancję blokady w normalnej zmiennej Test1 w __init__ i przekazać je jako argument Test2 .

  • instancję zamek w bloku if __name__ == "__main__" i przekazać je do Test1, a następnie z Test1 do Test2.

  • instancję zamek w bloku if __name__ == "__main__" i pierwszy wystąpienia Test2 z blokadą, po czym przechodzi do instancji i blokadę do Test1Test2. (Jest to najbardziej rozłączony sposób na zrobienie tego i polecam skorzystanie z tej metody, co najmniej ułatwi testowanie jednostkowe).

Oto kod do ostatniej sugestii:

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

W Pythonie podczas wykonywania skryptu Pythona przy użyciu $ python test1.py co stało się twój numer test1.py będzie ważny ted jako __main__ zamiast test1, więc jeśli chcesz uzyskać zdefiniowaną blokadę w uruchomionym skrypcie, nie powinieneś import test1, ale powinieneś , ponieważ jeśli zrobisz pierwszy, utworzysz inną blokadę, która różni się od __main__.lock (test1.lock != __main__.lock) .

Więc jedna poprawka do problemu (który daleki od bycia najlepszą) oraz zobaczyć, co się dzieje można zmienić skrypt 2 do niniejszego:

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

Dzięki za wyjaśnienie. Python jest dla mnie całkiem nowy, nigdy nie natknąłem się na to zachowanie. Cieszę się, że zapytałem, ponieważ może się również zdarzyć w innych, nie przewracających się sytuacjach. – user1997293

+0

@ user1997293: Tak, masz rację to zachowanie jest bardzo częste i cieszę się, że moja odpowiedź była pomocna :) – mouad

0

Jak powiedzieli inni, problem nie występuje w threading, ale w szczególnym przypadku importu cyklicznego.

Dlaczego wyjątkowy? Ponieważ zwykle workflow (mod1 import mod2 i mod2 import mod1) wygląda jak kolejny:

  1. Chcesz użyć MOD1 modułu, należy importuje go (import mod1)

  2. Kiedy Python znajdzie, interpreter dodaje go do sys.modules i rozpoczyna wykonywanie kodu

  3. Po osiągnięciu linii z import mod2, zatrzymuje wykonywanie mod1 i rozpoczyna wykonywanie mod2

  4. Gdy interpreter osiągnie import mod1 ciągu mod2, to nie obciążenie mod1 ponieważ został już dodany do sys.modules

  5. Po tym (chyba jakiś kod w mod2 dostęp niezainicjowanej jakiś zasób z mod1) wykończeń tłumacza wykonanie mod2 i mod1.

Ale w twoim przypadku w punkcie 4. Python wykonuje test1 jeden więcej czasu, ponieważ nie ma test1 w sys.modules! Powodem jest to, że nie zaimportowałeś go w pierwszej kolejności, ale uruchom go z wiersza poleceń.

Po prostu nie używaj cyklicznego importu - jak widzisz, jest to prawdziwy bałagan.

+0

W istocie zwykle unikam cyklicznego importu. Tym razem był to mały program testowy, który początkowo miał na celu sprawdzenie, czy zamek zachowywałby się jak singleton, czego nie zrobił. Ten pierwszy test nie miał cyklicznego importu. Potem zmieniłem program trochę na coś, co zadziała, ale niestety. – user1997293

Powiązane problemy