2013-01-21 32 views
5

Tôi đã viết một chương trình thử nghiệm đơn giản bằng cách sử dụng khóa khóa. Chương trình này không hoạt động như mong đợi, và trình thông dịch python không phàn nàn.Hành vi không mong muốn khi sử dụng khóa chủ đề python và nhập khẩu 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") 

Cả hai ngủ được thực hiện cùng một lúc! Không phải những gì tôi mong đợi khi sử dụng khóa.

Khi test2Method được chuyển đến test1.py mọi thứ hoạt động tốt. Khi tôi tạo khóa trong test2.py và nhập nó trong test1.py, mọi thứ hoạt động tốt. Khi tôi tạo khóa trong một tệp nguồn riêng biệt và nhập nó cả trong test1.py và test2.py, mọi thứ đều hoạt động tốt.

Có lẽ nó phải liên quan đến nhập khẩu circulair.

Nhưng tại sao không python phàn nàn về điều này?

Trả lời

1

Sử dụng print báo cáo trước và sau khi import báo cáo và in id(lock) ngay sau khi nó được tạo ra cho thấy rằng có trên thực tế hai khóa được tạo ra. Có vẻ như mô-đun được nhập hai lần và mouad giải thích rằng câu trả lời là vì test1.py được nhập trước tiên là __main__ và sau đó là test1, khiến khóa được khởi tạo hai lần.

Hãy làm như vậy, bằng cách sử dụng khóa toàn cầu không phải là giải pháp tốt. Có một số giải pháp tốt hơn, và tôi nghĩ rằng bạn sẽ tìm thấy một trong số họ sẽ phù hợp với nhu cầu của bạn.

  • Khởi tạo các khóa như là một biến lớp học của Test1, và vượt qua nó như một tham số để Test2

  • Khởi tạo các khóa như là một biến bình thường của Test1 trong __init__, và vượt qua nó như một tham số để Test2 .

  • Khởi tạo khóa trong khối if __name__ == "__main__" và chuyển đến Test1 và sau đó từ Test1 đến Test2.

  • nhanh chóng các khóa trong khối if __name__ == "__main__" và lần đầu tiên nhanh chóng Test2 với khóa, sau đó vượt qua Test2 dụ khóa để Test1. (Đây là cách tách rời nhất để làm điều đó, và tôi khuyên bạn nên sử dụng phương pháp này. Nó sẽ giảm thiểu việc kiểm tra đơn vị, ít nhất là.).

Dưới đây là các mã cho các đề nghị mới nhất:

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

Trong Python khi bạn thực hiện một kịch bản python sử dụng $ python test1.py những gì xảy ra là rằng test1.py của bạn sẽ là impor ted là __main__ thay vì test1, vì vậy nếu bạn muốn khóa được xác định trong tập lệnh được khởi chạy, bạn không nên import test1 nhưng bạn nên import __main__ vì nếu bạn làm cái đầu tiên, bạn sẽ tạo một khóa khác với __main__.lock (test1.lock != __main__.lock) .

Vì vậy, một sửa chữa cho vấn đề của bạn (mà xa là tốt nhất) và xem những gì đang xảy ra bạn có thể thay đổi 2 kịch bản của bạn này:

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

Cảm ơn bạn đã giải thích. Python khá mới đối với tôi, tôi chưa bao giờ bắt gặp hành vi này. Tôi vui vì tôi đã hỏi, bởi vì nó có thể xảy ra trong các tình huống khác, không phải luồng, là tốt. – user1997293

+0

@ user1997293: Có bạn đúng hành vi này là rất phổ biến và vui mừng câu trả lời của tôi là hữu ích :) – mouad

0

Như những người khác đã nói, sự cố không nằm trong số threading, nhưng trong trường hợp đặc biệt của bạn về nhập khẩu tuần hoàn.

Tại sao đặc biệt? Bởi vì công việc thông thường (mod1 nhập khẩu mod2mod2 nhập khẩu mod1) trông giống như sau:

  1. Bạn muốn sử dụng mod1 module, bạn nhập nó (import mod1)

  2. Khi Python tìm thấy nó, thông dịch viên cho biết thêm nó đến sys.modules và bắt đầu thực thi mã

  3. Khi nó đạt đến dòng import mod2, nó dừng thực hiện mod1 và bắt đầu thực hiện mod2

  4. Khi phiên dịch đạt import mod1 trong mod2, nó không tải mod1 bởi vì nó đã được bổ sung vào sys.modules

  5. Sau đó (trừ một số mã trong mod2 truy cập một số tài nguyên chưa được khởi tạo từ mod1) kết thúc phiên dịch thực hiện mod2mod1.

Nhưng trong trường hợp của bạn ở bước 4. Python thực hiện test1 thêm một thời gian vì không có test1 trong sys.modules! Lý do cho điều này là bạn đã không nhập nó ở nơi đầu tiên, nhưng chạy nó từ một dòng lệnh.

Vì vậy, chỉ cần không sử dụng nhập khẩu tuần hoàn - như bạn thấy nó là một mớ hỗn độn thực sự.

+0

Thật vậy tôi thường tránh nhập khẩu tuần hoàn. Lần này nó là một chương trình thử nghiệm nhỏ ban đầu dự định xem liệu khóa sẽ hoạt động như một singleton, mà nó không. Thử nghiệm đầu tiên này không có nhập khẩu tuần hoàn. Sau đó, tôi thay đổi chương trình một chút để một cái gì đó mà sẽ làm việc, nhưng than ôi. – user1997293

Các vấn đề liên quan