Queue.Queue vs. collections.deque


181

Birden çok iş parçacığı içine şeyler koyabilirsiniz ve birden çok iş parçacığı okuyabilir bir kuyruk gerekir.

Python'un en az iki kuyruk sınıfı vardır: Queue.Queue ve collections.deque, birincisi ikincisini dahili olarak kullanıyor. Her ikisi de belgelerde iş parçacığı açısından güvenli olduğunu iddia ediyor.

Ancak, Sıra belgeleri şunları da belirtir:

collections.deque, kilitleme gerektirmeyen hızlı atomik append () ve popleft () işlemleriyle sınırsız kuyrukların alternatif bir uygulamasıdır.

Hangi sanırım oldukça dayanamıyorum: bu deque sonuçta tamamen güvenli değil anlamına mı geliyor?

Eğer öyleyse, iki sınıf arasındaki farkı tam olarak anlayamayabilirim. Sıranın engelleme işlevi eklediğini görebiliyorum. Öte yandan, operatör için destek gibi bazı deque özelliklerini kaybeder.

Dahili deque nesnesine doğrudan erişmek,

Sırada x (). deque

evreli?

Ayrıca, deque zaten iş parçacığı için güvenli olduğunda Queue neden işlemleri için bir muteks kullanır?


RuntimeError: deque mutated during iterationelde edebileceğiniz şey dequebirkaç iplik arasında paylaşılan ve kilitleme yok ...
toine

4
@toine, konu ile hiçbir ilgisi yok. dequeAynı iş parçacığında bile yinelenirken bir süre eklediğinizde / sildiğinizde bu hatayı alabilirsiniz . Tek nedeni bu hatayı alamayan QueueDİR Queueyinelemeyi desteklemez.
maksimum

Yanıtlar:


281

Queue.Queueve collections.dequefarklı amaçlara hizmet eder. Sıra, farklı iş parçacıklarının kuyruğa alınan iletileri / verileri kullanarak iletişim kurmasına izin verirken collections.deque, yalnızca bir veri yapısı olarak tasarlanmıştır. İşte en niçin Queue.Queuegibi yöntemleri vardır put_nowait(), get_nowait()ve join()buna karşın, collections.dequeyapmaz. Queue.Queuebir koleksiyon olarak kullanılması amaçlanmamıştır, bu yüzden koleksiyonun beğenilerinden yoksundur.in operatörün .

Şuna bağlı: birden fazla iş parçacığınız varsa ve kilitlere ihtiyaç duymadan iletişim kurabilmelerini istiyorsanız, aradığınız Queue.Queue; veri yapısı olarak sadece bir sıra veya çift uçlu bir sıra istiyorsanız,collections.deque .

Son olarak, a'nın iç deque'sine erişmek ve manipüle etmek Queue.Queueateşle oynamaktır - bunu gerçekten yapmak istemezsiniz.


6
Hayır, bu hiç de iyi bir fikir değil. Kaynağına bakarsanız , kaputun altında Queue.Queuekullanır deque. collections.dequebir koleksiyon, Queue.Queuebir iletişim mekanizmasıdır. Tepegöz, Queue.Queueipliği güvenli hale getirmektir. dequeİplikler arasında iletişim için kullanılması sadece ağrılı ırklara yol açacaktır. İş dequeparçacığı güvenli olduğunda, bu, tercümanın nasıl uygulandığına dair mutlu bir kazadır ve güvenilecek bir şey değildir . Bu yüzden Queue.Queueilk etapta var.
Keith Gaughan

2
Sadece iş parçacıkları arasında iletişim kuruyorsanız, deque kullanarak ateşle oynadığınızı unutmayın. deque, GIL'in varlığı nedeniyle kazara güvenli bir iş parçacığıdır. GIL'siz bir uygulama tamamen farklı performans özelliklerine sahip olacaktır, bu nedenle diğer uygulamalarda indirim yapmak akıllıca değildir. Ayrıca, Queue vs deque'i tek bir iş parçacığında kullanımının saf bir ölçütünün aksine iş parçacıkları arasında kullanım için zamanladınız mı? Kodunuz Queue ve deque hızına karşı o kadar hassassa, aradığınız dil Python olmayabilir.
Keith Gaughan

3
@KeithGaughan deque is threadsafe by accident due to the existence of GIL; dequeiplik güvenliğini sağlamak için GIL'e güvenen doğrudur - ama değildir by accident. Resmi python belgeleri açıkça deque pop*/ append*yöntemlerin iş parçacığı açısından güvenli olduğunu belirtir . Bu nedenle, geçerli herhangi bir python uygulaması aynı garantiyi sağlamalıdır (GIL'siz uygulamalar GIL olmadan nasıl yapılacağını bulmak zorunda kalacaktır). Bu garantilere güvenle güvenebilirsiniz.
maksimum

2
@fantabolous önceki yorumuma rağmen, dequeiletişim için nasıl kullanacağınızı tam olarak anlamıyorum . Eğer popiçine sarılırsanız try/except, sadece yeni verileri bekleyen muazzam miktarda CPU tüketen yoğun bir döngü elde edersiniz. Bu Queue, veriyi bekleyen iş parçacığının CPU zamanını harcamak yerine uyku moduna geçmesini sağlayan engelleme çağrılarına kıyasla çok verimsiz bir yaklaşım gibi görünüyor .
maksimum

3
Queue.QueueO zaman için kaynak kodunu okumak isteyebilirsiniz , çünkü şu şekilde yazılmıştır collections.deque: hg.python.org/cpython/file/2.7/Lib/Queue.py - kaydırmayadeque erişilmesine izin vermek için koşul değişkenlerini kullanır iplik sınırlarını güvenli ve verimli bir şekilde aşar. dequeİletişim için nasıl kullanacağınıza ilişkin açıklama , kaynakta bulunmaktadır.
Keith Gaughan

44

Aradığınız tek şey nesneleriparçacıkları arasında aktarmanın güvenli bir yoluysa , her ikisi de işe yarayacaktır (hem FIFO hem de LIFO için). FIFO için:

Not:

  • Diğer işlemler dequeiş parçacığı için güvenli olmayabilir, emin değilim.
  • dequeengellemez pop()veya popleft()yeni bir öğe gelene kadar tüketici iplik akışınızı engellemeye dayamazsınız.

Bununla birlikte, deque'nin önemli bir verimlilik avantajına sahip olduğu görülmektedir . İşte 100 bin öğe eklemek ve kaldırmak için CPython 2.7.3 kullanan bazı kıyaslama sonuçları

deque 0.0747888759791
Queue 1.60079066852

Kıyaslama kodu:

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
"Üzerindeki diğer işlemler dequeiş parçacığı için güvenli olmayabilir". Bunu nereden alıyorsun?
Matt

@Matt - anlamımı daha iyi iletmek için yeniden ifade edildi
Jonathan

3
Tamam teşekkürler. Beni deque kullanmaktan alıkoyuyordu çünkü bilmediğim bir şey bildiğini sanıyordum. Sanırım sadece başka türlü bulana kadar iş parçacığı güvenli olduğunu varsayacağım.
Matt

@Matt "Deque'nin appendleft (), appendleft (), pop (), popleft () ve len (d) işlemleri CPython'da iş parçacığı açısından güvenlidir." Kaynak: bugs.python.org/issue15329
Filippo Vitale

7

Bilgi için deque iplik güvenliği için referans verilen bir Python bileti vardır ( https://bugs.python.org/issue15329 ). Başlık "hangi deque yöntemlerinin iş parçacığı için güvenli olduğunu açıklığa kavuşturun"

Alt satır burada: https://bugs.python.org/issue15329#msg199368

Deque'nin append (), appendleft (), pop (), popleft () ve len (d) işlemleri CPython'da iş parçacığı için güvenlidir. Ekleme yöntemlerinin sonunda bir DECREF vardır (maxlen'in ayarlandığı durumlar için), ancak bu tüm yapı güncellemeleri yapıldıktan ve değişmezler geri yüklendikten sonra gerçekleşir, bu nedenle bu işlemlere atomik davranmak uygundur.

Her neyse,% 100 emin değilseniz ve performansa göre güvenilirliği tercih ediyorsanız, sadece benzer bir Kilit koyun;)


6

Tüm tek elemanlı yöntemler dequeatomik ve iplik korumalıdır. Diğer tüm yöntemler de iş parçacığı için güvenlidir. Yapılacaklar gibi len(dq), dq[4]anlık doğru değerleri verir. Ancak, örneğin dq.extend(mylist): tüm öğelerinmylist diğer iş parçacıkları da aynı tarafa öğeler eklediğinde arka arkaya dosyalandığına - ancak genellikle iş parçacıkları arası iletişimde ve sorgulanan görev için bir gereklilik değildir.

Yani a ( kaputun altında bir kullanan) deque~ 20x daha hızlı ve "rahat" senkronizasyon API (engelleme / zaman aşımı), katı saygı veya "Bu yöntemleri geçersiz kıl (_put, _get, .. ) diğer sıra kuruluşlarının alt sınıflandırma davranışını uygulamak veya bu tür şeylere kendiniz baktığınızda, çıplaklık yüksek hızlı zincirler arası iletişim için iyi ve etkili bir anlaşmadır.Queuedequemaxsizedeque

Aslında ekstra bir muteks ve ekstra yöntem ._get()vs. yönteminin yoğun kullanımı, Queue.pygeriye dönük uyumluluk kısıtlamaları, geçmişte aşırı tasarım ve iş parçacıkları arası iletişimde bu önemli hız darboğaz sorunu için etkili bir çözüm sağlamak için bakım eksikliğinden kaynaklanmaktadır. Eski Python sürümlerinde bir liste kullanıldı - ancak list.append () /. Pop (0) bile atomik ve threadsafe idi ...


3

Ekleme notify_all()her birine deque appendve popleftçok daha kötü sonuçlarda sonuçların dequedaha 20x iyileşme varsayılan elde dequedavranış :

deque + notify_all: 0.469802
Queue:              0.667279

@Jonathan kodunu biraz değiştirin ve ben cPython 3.6.2 kullanarak kriter almak ve Queque do davranışı simüle etmek için deque döngü koşul ekleyin.

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)

Ve bu fonksiyonla sınırlı performans gibi görünüyor condition.notify_all()

collections.deque, kilitleme gerektirmeyen hızlı atomik append () ve popleft () işlemleriyle sınırsız kuyrukların alternatif bir uygulamasıdır. docs Sırası


2

dequeiş parçacığı için güvenli. "kilitleme gerektirmeyen işlemler", kilitleme işlemini kendiniz yapmanız gerekmediği anlamına gelir, bununla dequeilgilenir.

Bir göz alarak Queuekaynağı, iç deque denir self.queueböylece ve erişimcilere ve mutasyonları için bir muteksini kullanır Queue().queueedilir değil evreli kullanımı ile ilgilidir.

Bir "giriş" operatörü arıyorsanız, bir deque veya kuyruk muhtemelen probleminiz için en uygun veri yapısı değildir.


1
Peki, ne yapmak istiyorum kuyruk hiçbir yinelenen eklenmediğinden emin olmaktır. Bu, bir kuyruğun potansiyel olarak destekleyebileceği bir şey değil mi?
miracle2k

1
Ayrı bir kümeye sahip olmak ve kuyruğa bir şey eklediğinizde / kuyruğa aldığınızda bunu güncellemek muhtemelen en iyisidir. Bu, O (n) yerine O (log n) olacaktır, ancak kümeyi ve kuyruğu senkronize tutmaya (yani kilitleme) dikkat etmelisiniz.
brian-brazil

Bir Python seti karma tablodur, dolayısıyla O (1) olur. Ama evet, yine de kilitleme yapmak zorunda kalacaksınız.
AFoglia

1

(Yorum yapmak için itibarım yok ...) Farklı iş parçacıklarından hangi deque yöntemlerini kullandığınıza dikkat etmelisiniz.

deque.get () threadsafe gibi görünüyor, ancak bunu

for item in a_deque:
   process(item)

aynı anda başka bir iş parçacığı öğe ekliyorsa başarısız olabilir. "Yineleme sırasında mutasyona uğramış deque" şikayet bir RuntimeException var.

Hangi işlemlerin bundan etkilendiğini görmek için collectionsmodule.c dosyasını kontrol edin


bu tür bir hata dişler ve ana diş güvenliği için özel değildir. Örneğin, sadece yaparak >>> di = {1:None} >>> for x in di: del di[x]
kxr

1
Temelde asla başka bir iş parçacığı tarafından değiştirilebilecek bir şey üzerinde döngü yapmamalısınız (bazı durumlarda bunu kendi korumanızı ekleyerek yapabilirsiniz). Bir Kuyruk gibi, bir öğeyi işlemeden önce kuyruktan çıkarmanız / almanız istenir ve bunu normalde bir whiledöngü ile yaparsınız .
fantastik,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.