2013-02-25 28 views
5

mã của tôi làlý do tại sao sử dụng nhiều luồng để có được tổng là chính xác?

import threading 

counter = 0 

def worker(): 
    global counter 
    counter += 1 

if __name__ == "__main__": 
    threads = [] 
    for i in range(1000): 
     t = threading.Thread(target = worker) 
     threads.append(t) 
     t.start() 
    for t in threads: 
     t.join() 

    print counter 

bởi vì tôi không sử dụng khóa để bảo vệ tài nguyên chia sẻ, tức là. biến truy cập, tôi hy vọng rằng kết quả là một số ít hơn 1000, nhưng truy cập luôn luôn là 1000, tôi không biết tại sao. counter += 1 là một hoạt động nguyên tử trong Python?

hoạt động nào trong Python là nguyên tử khi sử dụng GIL?

+0

Vì GIL tôi đoán – wim

+0

Cụ thể, CPython thực hiện. – Amber

+0

@ Giống như những hoạt động nào là nguyên tử khi sử dụng GIL? – remykits

Trả lời

8

Không được tính vào số x += 1 là an toàn chỉ. Dưới đây là an example nơi nó không hoạt động (xem bình luận Josiah Carlson):

import threading 
x = 0 
def foo(): 
    global x 
    for i in xrange(1000000): 
     x += 1 
threads = [threading.Thread(target=foo), threading.Thread(target=foo)] 
for t in threads: 
    t.daemon = True 
    t.start() 
for t in threads: 
    t.join() 
print(x) 

Nếu bạn tháo rời foo:

In [80]: import dis 

In [81]: dis.dis(foo) 
    4   0 SETUP_LOOP    30 (to 33) 
       3 LOAD_GLOBAL    0 (xrange) 
       6 LOAD_CONST    1 (1000000) 
       9 CALL_FUNCTION   1 
      12 GET_ITER    
     >> 13 FOR_ITER    16 (to 32) 
      16 STORE_FAST    0 (i) 

    5   19 LOAD_GLOBAL    1 (x) 
      22 LOAD_CONST    2 (1) 
      25 INPLACE_ADD   
      26 STORE_GLOBAL    1 (x) 
      29 JUMP_ABSOLUTE   13 
     >> 32 POP_BLOCK   
     >> 33 LOAD_CONST    0 (None) 
      36 RETURN_VALUE   

Bạn thấy rằng có một LOAD_GLOBAL để lấy giá trị của x , có một số INPLACE_ADD và sau đó là STORE_GLOBAL.

Nếu cả hai chủ đề LOAD_GLOBAL liên tiếp, thì cả hai có thể tải cùng giá trị của x. Sau đó, cả hai đều tăng lên cùng một số và lưu trữ cùng một số. Vì vậy, công việc của một chủ đề sẽ ghi đè tác phẩm của một chủ đề khác. Đây không phải là chủ đề an toàn.

Như bạn có thể thấy, giá trị cuối cùng của x sẽ là 2000000 nếu chương trình là thread-an toàn, nhưng thay vào đó bạn hầu như luôn luôn có được một số ít hơn 2000000.


Nếu bạn thêm một khóa, bạn sẽ có được "mong đợi" câu trả lời:

import threading 
lock = threading.Lock() 
x = 0 
def foo(): 
    global x 
    for i in xrange(1000000): 
     with lock: 
      x += 1 
threads = [threading.Thread(target=foo), threading.Thread(target=foo)] 
for t in threads: 
    t.daemon = True 
    t.start() 
for t in threads: 
    t.join() 
print(x) 

mang

2000000 

Tôi nghĩ lý do tại sao đoạn code bạn được đăng không trưng bày một vấn đề:

for i in range(1000): 
    t = threading.Thread(target = worker) 
    threads.append(t) 
    t.start() 

là vì worker của bạn hoàn toàn như vậy darn nhanh chóng so với thời gian cần thiết để đẻ trứng một chủ đề mới mà trong thực tế có không có sự cạnh tranh giữa các chủ đề. Trong ví dụ của Josiah Carlson ở trên, mỗi sợi tiêu tốn một lượng thời gian đáng kể trong foo làm tăng cơ hội va chạm sợi.

+0

Bắt tốt. Tôi quên tháo rời các hướng dẫn để đảm bảo chúng thực sự là các cuộc gọi mã byte đơn trong trình thông dịch. Tôi đã xóa các nhận xét gây hiểu lầm. –

+0

Để thêm vào suy đoán của bạn vào lúc kết thúc lý do tại sao vấn đề này không hiển thị: Trình thông dịch có một số hoạt động bytecode nhất định mà nó thực hiện trước khi từ bỏ GIL, phần đầu của tôi là khoảng 100. Vì vậy, nhân viên chủ đề luôn luôn sẽ kết thúc tốt trong giới hạn này, và do đó dường như là nguyên tử. – lxop

+0

@unutbu cảm ơn bạn rất nhiều, nó thực sự giúp tôi rất nhiều !!! – remykits

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