2010-12-27 27 views
13

Khi nói đến luồng, tôi biết bạn phải đảm bảo rằng bạn không chỉnh sửa biến cùng một lúc mà một chuỗi khác đang chỉnh sửa nó, vì thay đổi của bạn có thể bị mất (khi tăng truy cập, ví dụ)Sửa đổi từ điển Python từ các chủ đề khác nhau

Điều tương tự cũng áp dụng cho từ điển? Hoặc là một từ điển một tập hợp các biến?

Nếu mọi chuỗi đều khóa từ điển, chương trình sẽ làm chậm chương trình một cách đáng kể, trong khi mỗi chuỗi chỉ cần quyền ghi vào phần nhỏ của từ điển.

Nếu không thể, có một số biến biến trong python, như trong php không?

Trả lời

6

Tôi nghĩ bạn đã hiểu sai điều này về toàn bộ chủ đề an toàn. Nó không quá nhiều về các biến (hoặc biến biến - dù là khủng khiếp, và cũng vô nghĩa - không nói xấu - ở đây như trong mọi trường hợp khác) nhưng về - ví dụ, có nhiều cách khó chịu khó chịu có thể đi sai rồi; tất cả chúng đều đến từ việc truy cập vào nội dung nào đó có thể thay đổi từ nhiều hơn một luồng vào thời gian chồng chéo - điều này:

  • thread N lấy dữ liệu từ nguồn (một số nơi trong bộ nhớ hoặc trên đĩa) tập tin, khá nhiều bất cứ điều gì có thể thay đổi)
  • chủ đề M nhận dữ liệu từ nguồn
  • chủ đề N đổi các dữ liệu
  • chủ đề M đổi các dữ liệu
  • chủ đề N ghi đè nguồn với dữ liệu sửa đổi
  • chủ đề M ghi đè nguồn với dữ liệu sửa đổi
  • Kết quả: thay đổi chủ đề N bị mất/giá trị chia sẻ mới không mất sợi sửa đổi N vào tài khoản

Và nó áp dụng cho từ điển và các biến biến (mà chỉ là một khủng khiếp, khủng khiếp thực hiện cấp độ ngôn ngữ của các dicts với các khóa chỉ chuỗi). Các giải pháp duy nhất không sử dụng trạng thái chia sẻ để bắt đầu (ngôn ngữ chức năng làm điều này bằng cách ngăn cản hoặc thậm chí hoàn toàn không cho phép biến đổi, và nó hoạt động tốt cho chúng) hoặc thêm một số loại khóa vào mọi thứ được chia sẻ (khó có được quyền, nhưng nếu bạn nhận được nó đúng, ít nhất nó hoạt động chính xác). Nếu không có hai chủ đề nào chia sẻ mọi thứ trong từ điển đó, bạn ổn - nhưng bạn nên tách riêng mọi thứ, để (nhiều hơn một chút) chắc chắn rằng chúng thực sự không chia sẻ bất kỳ thứ gì.

+0

Thật vậy.Và nên N và M sẽ sửa đổi dữ liệu ['a'] sẽ xảy ra, nhưng nếu chủ đề N sửa đổi dữ liệu ['a'] và luồng M sửa đổi dữ liệu ['b'] thì sao? Điều đó vẫn còn xảy ra? Và những gì có thể được thực hiện mà không cần khóa từ điển? – skerit

+0

@skerit: Như tôi đã viết, không. Họ không can thiệp lẫn nhau. Tất nhiên bạn không thể chắc chắn rằng họ thực sự không bao giờ chồng chéo nếu họ chia sẻ từ điển ... – delnan

+0

Ồ đúng, tôi hiểu rồi. "một vị trí trong từ điển". Sau đó, tôi chỉ phải khóa từ điển khi thêm hoặc xóa một khóa, vì điều đó sẽ gây ra ngoại lệ khi có thứ gì đó đang lặp qua nó. – skerit

0

Những gì bạn cần làm là không cho phép các chủ đề truy cập trực tiếp vào cấu trúc dữ liệu được chia sẻ mà thay vào đó hãy truy cập vào cấu trúc đó với điều gì đó đảm bảo loại trừ lẫn nhau như mutex.

Việc truy cập vào cấu trúc ban đầu trông giống nhau (shared[id] = value) mất nhiều công việc hơn nhưng không nhiều.

20

Điều tương tự cũng áp dụng cho từ điển? Hoặc là một từ điển một tập hợp các biến?

Hãy là tổng quát hơn:

gì "hoạt động nguyên tử" nghĩa là gì?

Từ Wikipedia:

Trong lập trình đồng thời, một hoạt động (hay tập hợp các hoạt động) là nguyên tử, linearizable, tách hoặc liên tục nếu nó xuất hiện ở phần còn lại của hệ thống để xảy ra ngay lập tức. Nguyên tử là một sự bảo đảm của sự cách ly tách biệt khỏi các quá trình đồng thời.

Bây giờ điều này có ý nghĩa gì trong Python?

Điều này có nghĩa là mỗi lệnh bytecode là nguyên tử (ít nhất là đối với Python < 3.2, trước GIL mới).

Tại sao lại như vậy ???

Vì Python (CPython) sử dụng Global Interpreter Lock (GIL). Trình thông dịch CPython sử dụng khóa để đảm bảo chỉ có một chuỗi chạy trong trình thông dịch tại một thời điểm và sử dụng "khoảng thời gian kiểm tra" (xem sys.getcheckinterval()) để biết số lượng lệnh bytecode thực thi trước khi chuyển đổi giữa các chuỗi (theo mặc định được đặt thành 100).

Vì vậy, bây giờ điều này có nghĩa là gì ??

Điều đó có nghĩa là các phép toán có thể được biểu diễn bởi chỉ một lệnh bytecode là nguyên tử. Ví dụ, incrementing một biến là không nguyên tử, bởi vì các hoạt động được thực hiện trong ba hướng dẫn bytecode:

>>> import dis 

>>> def f(a): 
     a += 1 

>>> dis.dis(f) 
    2   0 LOAD_FAST    0 (a) 
       3 LOAD_CONST    1 (1)  <<<<<<<<<<<< Operation 1 Load 
       6 INPLACE_ADD       <<<<<<<<<<<< Operation 2 iadd 
       7 STORE_FAST    0 (a)  <<<<<<<<<<<< Operation 3 store 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE   

Vì vậy, những gì về các từ điển ??

Một số hoạt động là nguyên tử; ví dụ, hoạt động này là nguyên tử:

d[x] = y 
d.update(d2) 
d.keys() 

Xem cho chính mình:

>>> def f(d): 
     x = 1 
     y = 1 
     d[x] = y 

>>> dis.dis(f) 
    2   0 LOAD_CONST    1 (1) 
       3 STORE_FAST    1 (x) 

    3   6 LOAD_CONST    1 (1) 
       9 STORE_FAST    2 (y) 

    4   12 LOAD_FAST    2 (y) 
      15 LOAD_FAST    0 (d) 
      18 LOAD_FAST    1 (x) 
      21 STORE_SUBSCR      <<<<<<<<<<< One operation 
      22 LOAD_CONST    0 (None) 
      25 RETURN_VALUE 

Xem this để hiểu những gì STORE_SUBSCR làm.

Nhưng như bạn thấy, nó không phải là hoàn toàn đúng, bởi vì hoạt động này:

   ... 
    4   12 LOAD_FAST    2 (y) 
      15 LOAD_FAST    0 (d) 
      18 LOAD_FAST    1 (x) 
      ... 

có thể làm cho toàn bộ hoạt động không nguyên tử. Tại sao? Giả sử biến x cũng có thể được thay đổi bởi một chuỗi khác ... hoặc bạn muốn một chuỗi khác xóa từ điển của bạn ... chúng tôi có thể đặt tên cho nhiều trường hợp khi nó có thể sai, vì vậy nó phức tạp! Và vì vậy ở đây chúng tôi sẽ áp dụng Murphy's Law: "Bất cứ điều gì có thể đi sai, sẽ đi sai".

Vậy bây giờ là gì?

Nếu bạn vẫn muốn chia sẻ biến giữa chủ đề, sử dụng một khóa:

import threading 

mylock = threading.RLock() 

def atomic_operation(): 
    with mylock: 
     print "operation are now atomic" 
+0

Cảm ơn bạn đã tận tâm. Bạn có nghe nói nếu có ai đã triển khai một lớp nhất quán cuối cùng trên luồng không? Vì vậy, các trường hợp sử dụng có thể chịu đựng dữ liệu cũ có thể có hiệu suất tốt hơn, nghĩa là. – HeyWatchThis

+0

@HeyWatchThis: Xin chào, không phải là tôi biết, xin lỗi :(, nhưng có lẽ bây giờ mới lớn nghĩ là chủ đề màu xanh lá cây (trở lại từ quá khứ :)) xem thư viện như gevent, eventlet ... có thể đơn giản hóa luồng cho bạn đặc biệt nếu bạn có một quá trình liên kết I/O, nếu không bạn vẫn có tùy chọn đa xử lý bằng ngôn ngữ năng động nhất có GIL (như Python, Ruby) hoặc có thể shinning mới như bộ nhớ giao dịch từ pypy, testing only :) Khác nếu bạn muốn xem mã đồng thời thực hiện để Erlang hoặc như vậy ... Chúc mừng, – mouad

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