Queue.Queue so với bộ sưu tập.deque


181

Tôi cần một hàng đợi mà nhiều luồng có thể đặt công cụ vào và nhiều luồng có thể đọc từ đó.

Python có ít nhất hai lớp xếp hàng, Queue.Queue và samples.deque, với lớp trước dường như sử dụng lớp sau trong nội bộ. Cả hai đều tuyên bố là an toàn chủ đề trong tài liệu.

Tuy nhiên, các tài liệu xếp hàng cũng nêu:

bộ sưu tập.deque là một triển khai thay thế của hàng đợi không giới hạn với các hoạt động append () và popleft () nguyên tử nhanh không yêu cầu khóa.

Mà tôi đoán tôi không hiểu lắm: Điều này có nghĩa là deque không hoàn toàn an toàn cho chủ đề?

Nếu có, tôi có thể không hoàn toàn hiểu sự khác biệt giữa hai lớp. Tôi có thể thấy rằng Queue thêm chức năng chặn. Mặt khác, nó mất một số tính năng deque như hỗ trợ cho người vận hành.

Truy cập trực tiếp vào đối tượng deque, là

x trong Hàng đợi (). deque

an toàn chủ đề?

Ngoài ra, tại sao Queue sử dụng một mutex cho các hoạt động của nó khi deque đã an toàn cho luồng?


RuntimeError: deque mutated during iterationlà những gì bạn có thể nhận được bằng cách sử dụng chia sẻ dequegiữa một số luồng và không khóa ...
toine

4
@toine không có gì để làm với chủ đề mặc dù. Bạn có thể gặp lỗi này bất cứ khi nào bạn thêm / xóa dequetrong một lần lặp ngay cả trong cùng một chuỗi. Lý do duy nhất bạn không thể nhận được lỗi Queuenày là vì Queuenó không hỗ trợ phép lặp.
tối đa

Câu trả lời:


281

Queue.Queuecollections.dequephục vụ các mục đích khác nhau. Queue.Queue được thiết kế để cho phép các luồng khác nhau giao tiếp bằng cách sử dụng tin nhắn / dữ liệu được xếp hàng, trong khi đó collections.dequechỉ đơn giản là dùng cho cơ sở hạ tầng. Đó là lý do tại sao Queue.Queuecó phương pháp như put_nowait(), get_nowait()join(), ngược lại collections.dequethì không. Queue.Queuekhông có ý định được sử dụng như một bộ sưu tập, đó là lý do tại sao nó thiếu các innhà điều hành.

Nó hiểu rõ điều này: nếu bạn có nhiều luồng và bạn muốn chúng có thể giao tiếp mà không cần khóa, bạn đang tìm kiếm Queue.Queue; nếu bạn chỉ muốn một hàng đợi hoặc một hàng đợi hai đầu làm cơ sở hạ tầng, hãy sử dụng collections.deque.

Cuối cùng, truy cập và thao túng deque nội bộ của a Queue.Queueđang chơi với lửa - bạn thực sự không muốn làm điều đó.


6
Không, đó không phải là một ý tưởng tốt cả. Nếu bạn nhìn vào nguồn của Queue.Queuenó, nó sử dụng dequedưới mui xe. collections.dequelà một bộ sưu tập, trong khi Queue.Queuelà một cơ chế truyền thông. Chi phí trong Queue.Queuelà để làm cho nó an toàn. Sử dụng dequeđể liên lạc giữa các chủ đề sẽ chỉ dẫn đến các cuộc đua đau đớn. Bất cứ khi nào dequetình cờ là chủ đề an toàn, đó là một tai nạn hạnh phúc về cách trình thông dịch được thực hiện và không phải là thứ gì đó để dựa vào. Đó là lý do tại sao Queue.Queuetồn tại ở nơi đầu tiên.
Keith Gaughan

2
Chỉ cần lưu ý rằng nếu bạn giao tiếp qua các chủ đề, bạn đang chơi với lửa bằng cách sử dụng deque. deque là chủ đề an toàn do tai nạn do sự tồn tại của GIL. Việc triển khai không có GIL sẽ có các đặc điểm hiệu suất hoàn toàn khác nhau, vì vậy việc giảm giá cho các triển khai khác là không khôn ngoan. Bên cạnh đó, bạn đã hẹn giờ Queue vs deque để sử dụng trên các luồng trái ngược với điểm chuẩn ngây thơ về việc sử dụng nó trong một luồng chưa? Nếu mã của bạn nhạy cảm với tốc độ của Queue vs deque, Python có thể không phải là ngôn ngữ mà bạn đang tìm kiếm.
Keith Gaughan

3
@KeithGaughan deque is threadsafe by accident due to the existence of GIL; đúng là dequedựa vào GIL để đảm bảo an toàn cho luồng - nhưng thực tế không phải vậy by accident. Tài liệu python chính thức nêu rõ rằng deque pop*/ append*phương thức là an toàn luồng. Vì vậy, bất kỳ triển khai python hợp lệ nào cũng phải cung cấp cùng một bảo đảm (các triển khai không có GIL sẽ phải tìm ra cách thực hiện mà không cần GIL). Bạn có thể an toàn dựa vào những đảm bảo.
tối đa

2
@fantabolous bình luận trước đây của tôi mặc dù, tôi không hiểu bạn sẽ sử dụng như thế nào dequeđể giao tiếp. Nếu bạn kết thúc popthành một try/except, bạn sẽ kết thúc với một vòng lặp bận rộn ăn hết lượng CPU khổng lồ chỉ chờ dữ liệu mới. Đây có vẻ là một cách tiếp cận không hiệu quả khủng khiếp so với các cuộc gọi chặn được cung cấp bởi Queue, đảm bảo rằng luồng chờ dữ liệu sẽ chuyển sang chế độ ngủ và không lãng phí thời gian của CPU.
tối đa

3
Sau đó, bạn có thể muốn đọc mã nguồn Queue.Queue, bởi vì nó được viết bằng cách sử dụng collections.deque: hg.python.org/cpython/file/2.7/Lib/Queue.py - nó sử dụng các biến điều kiện để cho phép dequetruy cập nó một cách hiệu quả trên ranh giới luồng an toàn và hiệu quả. Giải thích về cách bạn sử dụng một dequegiao tiếp là có ngay trong nguồn.
Keith Gaughan

44

Nếu tất cả những gì bạn đang tìm kiếm là một cách an toàn chủ đề để chuyển các đối tượng giữa các luồng , thì cả hai sẽ hoạt động (cả cho FIFO và LIFO). Dành cho quý:

Ghi chú:

  • Các hoạt động khác trên dequecó thể không phải là chủ đề an toàn, tôi không chắc chắn.
  • dequekhông chặn pop()hoặc popleft()vì vậy bạn không thể căn cứ luồng luồng tiêu dùng của mình vào việc chặn cho đến khi một mặt hàng mới xuất hiện.

Tuy nhiên, dường như deque có lợi thế hiệu quả đáng kể . Dưới đây là một số kết quả điểm chuẩn trong vài giây bằng CPython 2.7.3 để chèn và xóa các mục 100k

deque 0.0747888759791
Queue 1.60079066852

Đây là mã điểm chuẩn:

import time
import Queue
import collections

q = collections.deque()
t0 = time.clock()
for i in xrange(100000):
    q.append(1)
for i in xrange(100000):
    q.popleft()
print 'deque', time.clock() - t0

q = Queue.Queue(200000)
t0 = time.clock()
for i in xrange(100000):
    q.put(1)
for i in xrange(100000):
    q.get()
print 'Queue', time.clock() - t0

1
Bạn cho rằng "Các hoạt động khác trên dequecó thể không an toàn cho chuỗi". Bạn lấy cái đó từ đâu?
Matt

@Matt - đọc lại để truyền đạt tốt hơn ý nghĩa của tôi
Jonathan

3
Được rồi cảm ơn. Điều đó đã ngăn tôi sử dụng deque vì tôi nghĩ bạn biết điều gì đó tôi đã không làm. Tôi đoán tôi sẽ chỉ cho rằng nó an toàn cho đến khi tôi phát hiện ra.
Matt

@Matt "Các hoạt động của deque's append (), appendleft (), pop (), popleft () và len (d) đều an toàn cho luồng trong CPython." nguồn: bug.python.org/su15329
Filippo Vitale

7

Để biết thông tin, có một vé Python được tham chiếu cho an toàn chủ đề deque ( https://bugs.python.org/su15329 ). Tiêu đề "làm rõ các phương thức deque là an toàn chủ đề"

Dòng dưới cùng ở đây: https://bugs.python.org/su15329#msg199368

Các hoạt động append (), appendleft (), pop (), popleft () và len (d) của deque là an toàn luồng trong CPython. Các phương thức chắp thêm có một DECREF ở cuối (đối với trường hợp maxlen đã được đặt), nhưng điều này xảy ra sau khi tất cả các cập nhật cấu trúc đã được thực hiện và các bất biến đã được khôi phục, vì vậy có thể coi các hoạt động này là nguyên tử.

Dù sao, nếu bạn không chắc chắn 100% và bạn thích độ tin cậy hơn hiệu suất, chỉ cần đặt một khóa như ;;


6

Tất cả các phương pháp đơn yếu tố trên dequelà nguyên tử và an toàn luồng. Tất cả các phương pháp khác là an toàn chủ đề quá. Những điều như len(dq), dq[4]mang lại giá trị chính xác nhất thời. Nhưng hãy nghĩ ví dụ về dq.extend(mylist): bạn không nhận được đảm bảo rằng tất cả các yếu tố mylistđược nộp liên tiếp khi các luồng khác cũng nối các phần tử ở cùng một phía - nhưng đó thường không phải là một yêu cầu trong giao tiếp giữa các luồng và cho nhiệm vụ được hỏi.

Vì vậy, a dequenhanh hơn ~ 20 lần so với Queue(sử dụng dequedưới mui xe) và trừ khi bạn không cần API đồng bộ hóa "thoải mái" (chặn / hết thời gian), tuân theo nghiêm ngặt maxsizehoặc "Ghi đè các phương thức này (_put, _get, .. ) để thực hiện các tổ chức xếp hàng khác " hành vi phân lớp phụ hoặc khi bạn tự chăm sóc những thứ đó, thì trần dequelà một thỏa thuận tốt và hiệu quả cho giao tiếp giữa các luồng tốc độ cao.

Trong thực tế, việc sử dụng nhiều phương thức thêm và phương thức bổ sung, ._get()vv gọi Queue.pylà do các hạn chế tương thích ngược, thiết kế quá mức và thiếu cẩn thận để cung cấp một giải pháp hiệu quả cho vấn đề tắc nghẽn tốc độ quan trọng này trong giao tiếp liên luồng. Một danh sách đã được sử dụng trong các phiên bản Python cũ hơn - nhưng ngay cả list.append () /. Pop (0) là & là nguyên tử và chủ đề an toàn ...


3

Thêm notify_all()vào từng deque appendpopleftdẫn đến kết quả tồi tệ dequehơn nhiều so với cải thiện 20 lần đạt được theo dequehành vi mặc định :

deque + notify_all: 0.469802
Queue:              0.667279

@Jonathan sửa đổi mã của anh ấy một chút và tôi lấy điểm chuẩn bằng cPython 3.6.2 và thêm điều kiện trong vòng lặp deque để mô phỏng hành vi Queue làm.

import time
from queue import Queue
import threading
import collections

mutex = threading.Lock()
condition = threading.Condition(mutex)
q = collections.deque()
t0 = time.clock()
for i in range(100000):
    with condition:
        q.append(1)
        condition.notify_all()
for _ in range(100000):
    with condition:
        q.popleft()
        condition.notify_all()
print('deque', time.clock() - t0)

q = Queue(200000)
t0 = time.clock()
for _ in range(100000):
    q.put(1)
for _ in range(100000):
    q.get()
print('Queue', time.clock() - t0)

Và có vẻ như hiệu suất bị hạn chế bởi chức năng này condition.notify_all()

bộ sưu tập.deque là một triển khai thay thế của hàng đợi không giới hạn với các hoạt động append () và popleft () nguyên tử nhanh không yêu cầu khóa. tài liệu xếp hàng


2

dequelà chủ đề an toàn. "Các hoạt động không yêu cầu khóa" có nghĩa là bạn không phải tự khóa, hãy dequechăm sóc nó.

Lấy một cái nhìn tại các Queuenguồn, deque nội bộ được gọi self.queuevà sử dụng một mutex cho accessors và đột biến, vì vậy Queue().queueđược không thread-safe để sử dụng.

Nếu bạn đang tìm kiếm một toán tử "trong", thì một deque hoặc hàng đợi có thể không phải là cấu trúc dữ liệu phù hợp nhất cho vấn đề của bạn.


1
Vâng, điều tôi muốn làm là đảm bảo rằng không có bản sao nào được thêm vào hàng đợi. Đây không phải là một cái gì đó một hàng đợi có thể hỗ trợ?
miracle2k

1
Có lẽ tốt nhất là có một bộ riêng và cập nhật khi bạn thêm / xóa thứ gì đó khỏi hàng đợi. Đó sẽ là O (log n) chứ không phải O (n), nhưng bạn sẽ phải cẩn thận để giữ cho bộ và hàng đợi được đồng bộ hóa (tức là khóa).
brian-brazil

Một bộ Python là một bảng băm, vì vậy nó sẽ là O (1). Nhưng có, bạn vẫn sẽ phải khóa.
AFoglia

1

(dường như tôi không có tiếng tăm gì để bình luận ...) Bạn cần cẩn thận những phương pháp nào của deque bạn sử dụng từ các chủ đề khác nhau.

deque.get () dường như là chủ đề an toàn, nhưng tôi đã thấy rằng làm

for item in a_deque:
   process(item)

có thể thất bại nếu một chủ đề khác đang thêm các mục cùng một lúc. Tôi nhận được một RuntimeException phàn nàn "deque bị đột biến trong quá trình lặp lại".

Kiểm tra bộ sưu tậpmodule.c để xem hoạt động nào bị ảnh hưởng bởi điều này


loại lỗi này không đặc biệt đối với các luồng và an toàn luồng chính. Nó xảy ra, ví dụ như chỉ cần làm >>> di = {1:None} >>> for x in di: del di[x]
kxr

1
Về cơ bản, bạn không bao giờ nên lặp lại một cái gì đó có thể được sửa đổi bởi một luồng khác (mặc dù trong một số trường hợp bạn có thể làm điều đó bằng cách thêm sự bảo vệ của riêng bạn). Giống như Hàng đợi, bạn dự định bật / lấy một mục khỏi hàng đợi trước khi xử lý nó và bạn thường làm điều đó với một whilevòng lặp.
tưởng tượng
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.