Tôi nhận thấy rằng nó thường được đề xuất sử dụng hàng đợi với nhiều luồng, thay vì danh sách và .pop()
. Đây có phải là vì danh sách không an toàn cho chuỗi, hoặc vì một số lý do khác?
Tôi nhận thấy rằng nó thường được đề xuất sử dụng hàng đợi với nhiều luồng, thay vì danh sách và .pop()
. Đây có phải là vì danh sách không an toàn cho chuỗi, hoặc vì một số lý do khác?
Câu trả lời:
Danh sách bản thân là chủ đề an toàn. Trong CPython, GIL bảo vệ chống lại truy cập đồng thời vào chúng và các triển khai khác cẩn thận sử dụng khóa hạt mịn hoặc kiểu dữ liệu được đồng bộ hóa để triển khai danh sách của chúng. Tuy nhiên, trong khi bản thân danh sách không thể bị hỏng do cố gắng truy cập đồng thời, dữ liệu của danh sách không được bảo vệ. Ví dụ:
L[0] += 1
không được đảm bảo để thực sự tăng L [0] nếu một luồng khác thực hiện điều tương tự, vì +=
không phải là hoạt động nguyên tử. (Rất, rất ít thao tác trong Python thực sự là nguyên tử, vì hầu hết chúng có thể khiến mã Python tùy ý được gọi.) Bạn nên sử dụng Hàng đợi vì nếu bạn chỉ sử dụng danh sách không được bảo vệ, bạn có thể nhận hoặc xóa mục sai vì chủng tộc điều kiện.
Để làm rõ một điểm trong câu trả lời xuất sắc của Thomas, cần đề cập rằng đó append()
là chủ đề an toàn.
Điều này là do không có lo ngại rằng dữ liệu được đọc sẽ ở cùng một nơi khi chúng ta viết thư cho nó. Các append()
hoạt động không đọc dữ liệu, nó chỉ ghi dữ liệu vào danh sách.
PyList_Append
được thực hiện trong một khóa GIL. Nó được đưa ra một tham chiếu đến một đối tượng để chắp thêm. Nội dung của đối tượng đó có thể được thay đổi sau khi được đánh giá và trước khi cuộc gọi đến PyList_Append
được thực hiện. Nhưng nó vẫn sẽ là cùng một đối tượng và được nối thêm một cách an toàn (nếu bạn làm như vậy lst.append(x); ok = lst[-1] is x
, ok
dĩ nhiên có thể là Sai). Mã bạn tham chiếu không đọc từ đối tượng được nối thêm, ngoại trừ INCREF nó. Nó đọc và có thể phân bổ lại, danh sách được thêm vào.
L[0] += x
sẽ thực hiện __getitem__
bật L
và sau đó __setitem__
bật L
- nếu L
hỗ trợ, __iadd__
nó sẽ thực hiện mọi thứ hơi khác ở giao diện đối tượng, nhưng vẫn có hai thao tác riêng biệt L
ở cấp trình thông dịch python (bạn sẽ thấy chúng trong biên dịch mã byte). Việc append
này được thực hiện trong một cuộc gọi phương thức đơn trong mã byte.
remove
?
Dưới đây là danh sách toàn diện nhưng không đầy đủ các ví dụ về list
hoạt động và liệu chúng có an toàn hay không. Hy vọng nhận được câu trả lời về obj in a_list
cấu trúc ngôn ngữ ở đây .
Gần đây tôi đã gặp trường hợp này khi tôi cần nối thêm một danh sách liên tục trong một luồng, lặp qua các mục và kiểm tra xem mục đó đã sẵn sàng chưa, đó là AsyncResult trong trường hợp của tôi và chỉ xóa nó khỏi danh sách nếu nó đã sẵn sàng. Tôi không thể tìm thấy bất kỳ ví dụ nào chứng minh rõ ràng vấn đề của mình. Đây là một ví dụ minh họa việc thêm vào danh sách trong một luồng liên tục và liên tục xóa khỏi danh sách đó trong một luồng khác Phiên bản hoàn hảo chạy dễ dàng trên các số nhỏ hơn nhưng vẫn giữ các số đủ lớn và chạy vài lần và bạn sẽ thấy lỗi
Phiên bản FLAWED
import threading
import time
# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Đầu ra khi LRI
Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list
Phiên bản sử dụng ổ khóa
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Đầu ra
[] # Empty list
Phần kết luận
Như đã đề cập trong các câu trả lời trước trong khi hành động nối thêm hoặc bật các phần tử từ danh sách là an toàn cho luồng, điều không an toàn của luồng là khi bạn nối vào một luồng và bật trong một luồng khác
with r:
) thay vì gọi một cách rõ ràng r.acquire()
vàr.release()