Queue.Queue बनाम संग्रह


181

मुझे एक कतार की आवश्यकता है जिसमें कई धागे सामान डाल सकते हैं, और कई धागे से पढ़ सकते हैं।

पायथन में कम से कम दो कतार कक्षाएं, क्यू.क्यू.क्यू और संग्रह हैं। विशेष रूप से पूर्ववर्ती आंतरिक रूप से उपयोग करने के साथ। दोनों दस्तावेज़ में थ्रेड-सुरक्षित होने का दावा करते हैं।

हालाँकि, कतार डॉक्स भी बताती हैं:

collection.deque तेजी से परमाणु परिशिष्ट () और पॉपलेफ्ट () संचालन के साथ अनबाउंड कतारों का एक वैकल्पिक कार्यान्वयन है जिसमें लॉकिंग की आवश्यकता नहीं होती है।

जो मुझे लगता है कि मैं बिल्कुल समझ में नहीं आता है: क्या इसका मतलब यह है कि deque पूरी तरह से धागा-सुरक्षित नहीं है?

यदि यह है, तो मैं दो वर्गों के बीच के अंतर को पूरी तरह से नहीं समझ सकता। मैं देख सकता हूं कि क्यू ब्लॉकिंग कार्यक्षमता जोड़ता है। दूसरी ओर, यह इन-ऑपरेटर के लिए समर्थन जैसी कुछ ख़राब सुविधाएँ खो देता है।

आंतरिक deque ऑब्जेक्ट को सीधे एक्सेस करना, है

x कतार में ()। deque

सुरक्षित धागा?

इसके अलावा, क्यू क्यू ऑपरेशन के लिए एक म्यूटेक्स को नियोजित करता है, जब पहले से ही थ्रेड सुरक्षित है?


RuntimeError: deque mutated during iterationक्या तुम हो सकता है dequeकई धागे और कोई ताला के बीच एक साझा का उपयोग कर रहा है ...
toine

4
@toine हालांकि कुछ भी नहीं है कि धागे के साथ क्या करना है। जब भी आप जोड़ते / हटाते हैं, तो आप dequeउसी थ्रेड में भी पुनरावृत्ति करते हुए यह त्रुटि प्राप्त कर सकते हैं । एकमात्र कारण Queueजिससे आप यह त्रुटि नहीं प्राप्त कर सकते हैं वह यह है कि Queueपुनरावृत्ति का समर्थन नहीं करता है।
अधिकतम

जवाबों:


281

Queue.Queueऔर collections.dequeविभिन्न उद्देश्यों की पूर्ति करें। Queue.Queue कतारबद्ध संदेशों / डेटा का उपयोग करके विभिन्न थ्रेड्स को संवाद करने की अनुमति देने के लिए है, जबकि collections.dequeइसका उद्देश्य केवल एक डेटास्ट्रक्चर के रूप में है। इसलिए Queue.Queueजैसे तरीकों है put_nowait(), get_nowait()और join()जबकि, collections.dequeनहीं करता है। Queue.Queueएक संग्रह के रूप में उपयोग करने का इरादा नहीं है, यही वजह है कि इसमें inऑपरेटर की पसंद का अभाव है ।

यह इस पर उबलता है: यदि आपके पास कई धागे हैं और आप चाहते हैं कि वे ताले की आवश्यकता के बिना संवाद करने में सक्षम हों, तो आप देख रहे हैं Queue.Queue; यदि आप सिर्फ एक कतार या एक डबल-एंडेड कतार के रूप में डेटास्ट्रक्चर चाहते हैं, तो उपयोग करें collections.deque

अंत में, एक के आंतरिक deque तक पहुंचना और हेरफेर Queue.Queueकरना आग से खेल रहा है - आप वास्तव में ऐसा नहीं करना चाहते हैं।


6
नहीं, यह बिल्कुल अच्छा विचार नहीं है। यदि आप के स्रोत को देखते हैं Queue.Queue, तो यह dequeहुड के नीचे उपयोग करता है । collections.dequeएक संग्रह है, जबकि Queue.Queueएक संचार तंत्र है। ओवरहेड Queue.Queueइसे थ्रेडसेफ़ बनाने के लिए है। dequeधागे के बीच संचार के लिए उपयोग करने से केवल दर्दनाक दौड़ होगी। जब भी dequeथ्रेडसेफ़ होता है, तो यह एक सुखद दुर्घटना होती है कि दुभाषिया कैसे लागू किया जाता है, और न कि कुछ पर निर्भर होने के लिए। इसलिए Queue.Queueपहले स्थान पर मौजूद है।
कीथ गौघ्न

2
बस ध्यान रखें कि यदि आप थ्रेड्स में संचार कर रहे हैं, तो आप deque का उपयोग करके आग से खेल रहे हैं। जीआईएल के अस्तित्व के कारण दुर्घटना से विकर्षित होने का खतरा है । जीआईएल-कम कार्यान्वयन में पूरी तरह से अलग प्रदर्शन विशेषताएं होंगी, इसलिए अन्य कार्यान्वयन को छूट देना बुद्धिमानी नहीं है। इसके अलावा, क्या आपने एक धागे में इसके उपयोग के एक भोले बेंचमार्क के विपरीत क्यू बनाम डीक को धागे के उपयोग के लिए समयबद्ध किया है? अपने कोड है कि कतार की गति Deque बनाम के प्रति संवेदनशील, पायथन भाषा आप देख रहे हैं नहीं हो सकता है।
कीथ गौघ्न

3
@ कीथगोगन deque is threadsafe by accident due to the existence of GIL; यह सच है कि dequeधागा सुरक्षा सुनिश्चित करने के लिए GIL पर निर्भर करता है - लेकिन यह नहीं है by accident। आधिकारिक अजगर दस्तावेज में स्पष्ट रूप से कहा गया है कि deque pop*/ append*तरीके थ्रेड-सुरक्षित हैं। तो किसी भी मान्य अजगर कार्यान्वयन को एक ही गारंटी प्रदान करनी चाहिए (GIL- कम कार्यान्वयन को यह पता लगाना होगा कि बिना GIL के ऐसा कैसे करें)। आप उन गारंटी पर सुरक्षित रूप से भरोसा कर सकते हैं।
अधिकतम

2
@ मेरी पिछली टिप्पणी के बावजूद, मुझे यह समझ में नहीं आया कि आप dequeसंचार के लिए कैसे उपयोग करेंगे । यदि आप popएक में लपेटते हैं try/except, तो आप एक व्यस्त लूप के साथ सीपीयू की भारी मात्रा में भोजन करेंगे, बस नए डेटा की प्रतीक्षा करेंगे। ऐसा लगता है कि पेशकश की गई अवरुद्ध कॉल की तुलना में एक भयानक अक्षम दृष्टिकोण है Queue, जो यह सुनिश्चित करता है कि डेटा की प्रतीक्षा करने वाला थ्रेड सोने के लिए जाएगा और सीपीयू समय बर्बाद नहीं करेगा।
अधिकतम

3
आप Queue.Queueतब के लिए स्रोत कोड का एक पाठ लेना चाह सकते हैं , क्योंकि इसका उपयोग करके लिखा गया है collections.deque: hg.python.org/cpython/file/2.7/Lib/Queue.py - यह शर्त चर का उपयोग करता है कि dequeयह कुशलतापूर्वक इसे लपेटने की अनुमति देता है। सुरक्षित रूप से और कुशलता से धागा सीमाओं पर। dequeसंचार के लिए आप किस तरह उपयोग करेंगे, इसका स्पष्टीकरण स्रोत में है।
कीथ गौघन

44

यदि आप सभी की तलाश कर रहे हैं , तो थ्रेड्स के बीच ऑब्जेक्ट्स को स्थानांतरित करने के लिए एक थ्रेड-सुरक्षित तरीका है , तो दोनों काम करेंगे (दोनों फीफो और LIFO के लिए)। फीफो के लिए:

ध्यान दें:

  • अन्य ऑपरेशन dequeथ्रेड सुरक्षित नहीं हो सकते हैं, मुझे यकीन नहीं है।
  • dequeब्लॉक नहीं करता है pop()या popleft()इसलिए आप किसी नए आइटम के आने तक अपने उपभोक्ता थ्रेड प्रवाह को अवरुद्ध नहीं कर सकते।

हालांकि, ऐसा लगता है कि डेक्स का एक महत्वपूर्ण दक्षता लाभ है । 100k आइटम डालने और निकालने के लिए CPython 2.7.3 का उपयोग करके सेकंड में कुछ बेंचमार्क परिणाम दिए गए हैं

deque 0.0747888759791
Queue 1.60079066852

यहाँ बेंचमार्क कोड है:

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
आप दावा करते हैं कि "अन्य ऑपरेशन dequeथ्रेड सुरक्षित नहीं हो सकते हैं"। आपको वह कहाँ से मिलेगा?
मैट

@Matt - मेरे अर्थ को बेहतर ढंग से व्यक्त करने के लिए
जोनाथन

3
ठीक है धन्यवाद। यह मुझे डॉक का उपयोग करने से रोक रहा था क्योंकि मुझे लगा कि आप कुछ जानते हैं जो मैंने नहीं किया। मुझे लगता है कि मैं इसे केवल तब तक सुरक्षित मानूंगा जब तक मुझे पता नहीं चलता।
मैट

@Matt "CPYthon में" deque का परिशिष्ट (), परिशिष्ट (), pop (), popleft (), और len (d) संचालन थ्रेड-सुरक्षित है। " source: Bugs.python.org/issue15329
फिलिप्पो विटाले

7

जानकारी के लिए एक थ्रेड टिकट है जिसे deque thread-safety ( https://bugs.python.org/issue153232 ) के लिए संदर्भित किया गया है । शीर्षक "स्पष्ट करें कि कौन-सी विधियाँ थ्रेड-सुरक्षित हैं"

नीचे पंक्ति यहां: https://bugs.python.org/issue15329#msg199368

डीप के एपेंड (), एपेंडलेफ्ट (), पॉप (), पॉपलेफ्ट (), और लेन (डी) के संचालन सीपीथॉन में थ्रेड-सुरक्षित हैं। परिशिष्ट विधियों के अंत में एक DECREF होता है (ऐसे मामलों के लिए जहां अधिकतम सेट किया गया है), लेकिन ऐसा तब होता है जब संरचना के सभी अद्यतन किए गए होते हैं और आक्रमणकारियों को पुनर्स्थापित किया जाता है, इसलिए इन ऑपरेशनों को परमाणु के रूप में मानना ​​ठीक है।

वैसे भी, यदि आप 100% सुनिश्चित नहीं हैं और आप प्रदर्शन पर विश्वसनीयता पसंद करते हैं, तो बस एक लॉक की तरह रखें;)


6

सभी एकल-तत्व विधियां dequeपरमाणु और धागा-सुरक्षित हैं। अन्य सभी विधियाँ थ्रेड-सुरक्षित भी हैं। चीजें जैसे len(dq), dq[4]क्षणिक सही मान पैदा करती हैं। लेकिन इस बारे में सोचें dq.extend(mylist): आपको इस बात की कोई गारंटी नहीं है कि सभी तत्व mylistएक पंक्ति में दर्ज किए जाते हैं जब अन्य धागे भी उसी तरफ तत्व जोड़ते हैं - लेकिन आम तौर पर अंतर-थ्रेड संचार में और पूछताछ कार्य के लिए कोई आवश्यकता नहीं होती है।

तो एक deque~ ~ 20x से तेज है Queue(जो dequeहुड के नीचे का उपयोग करता है ) और जब तक आपको "आरामदायक" तुल्यकालन एपीआई (अवरुद्ध / समयबाह्य) की आवश्यकता नहीं होती, सख्त maxsizeपालन ​​या "इन तरीकों को ओवरराइड करें (_put, _get, ।।) ) अन्य कतार संगठनों को " उप-क्लासिंग व्यवहार को लागू करने के लिए , या जब आप स्वयं ऐसी चीजों का ध्यान रखते हैं, तो एक नंगे dequeउच्च गति के अंतर-थ्रेड संचार के लिए एक अच्छा और कुशल सौदा है।

वास्तव में एक अतिरिक्त म्यूटेक्स और अतिरिक्त विधि ._get()आदि विधि कॉल का भारी उपयोग Queue.pyबैकवर्ड संगतता बाधाओं, पिछले ओवर-डिज़ाइन और अंतर-धागा संचार में इस महत्वपूर्ण गति अड़चन मुद्दे के लिए एक कुशल समाधान प्रदान करने के लिए देखभाल की कमी के कारण है। पुराने पायथन संस्करणों में एक सूची का उपयोग किया गया था - लेकिन यहां तक ​​कि list.append () / (पॉप। (0) था और परमाणु और थ्रेडसेफ़ है ...


3

जोड़ा जा रहा है notify_all()प्रत्येक के लिए deque appendऔर popleftके लिए भी बदतर परिणामों में परिणाम dequeकी तुलना में 20 गुना सुधार डिफ़ॉल्ट रूप से हासिल dequeव्यवहार :

deque + notify_all: 0.469802
Queue:              0.667279

@ जोनाथन अपने कोड को थोड़ा संशोधित करते हैं और मुझे cPython 3.6.2 का उपयोग करके बेंचमार्क मिलता है और व्यवहार पंक्ति को अनुकरण करने के लिए deque लूप में स्थिति जोड़ते हैं।

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)

और यह इस फ़ंक्शन द्वारा सीमित प्रदर्शन लगता है condition.notify_all()

collection.deque तेजी से परमाणु परिशिष्ट () और पॉपलेफ्ट () संचालन के साथ अनबाउंड कतारों का एक वैकल्पिक कार्यान्वयन है जिसमें लॉकिंग की आवश्यकता नहीं होती है। डॉक्स कतार


2

dequeधागा सुरक्षित है। "ऑपरेशन जिन्हें लॉकिंग की आवश्यकता नहीं है" का अर्थ है कि आपको स्वयं लॉकिंग करने की आवश्यकता नहीं है, इसका dequeख्याल रखना है।

पर एक नज़र ले रहा है Queueस्रोत, आंतरिक Deque कहा जाता है self.queueऔर accessors और म्यूटेशन के लिए एक म्युटेक्स का उपयोग करता है, तो Queue().queueहै नहीं थ्रेड-सुरक्षित उपयोग करने के लिए।

यदि आप एक "इन" ऑपरेटर के लिए देख रहे हैं, तो आपकी समस्या के लिए संभवतः एक उपयुक्त डेटा संरचना नहीं है।


1
खैर, मैं जो करना चाहता हूं वह सुनिश्चित करें कि कतार में कोई डुप्लिकेट नहीं जोड़ा जाता है। क्या यह कुछ ऐसा नहीं है जो संभावित रूप से समर्थन कर सकता है?
15

1
संभवतः एक अलग सेट होना सबसे अच्छा होगा, और जब आप कतार से कुछ जोड़ते / हटाते हैं, तो उसे अपडेट करें। वह O (n) के बजाय O (लॉग एन) होगा, लेकिन आपको सेट और कतार को सिंक (यानी लॉकिंग) में रखने के लिए सावधान रहना होगा।
ब्रायन-ब्राज़ील

एक पायथन सेट एक हैश टेबल है, इसलिए यह O (1) होगा। लेकिन हाँ, आपको अभी भी लॉक करना होगा।
AFoglia

1

(ऐसा लगता है कि मेरे पास टिप्पणी करने के लिए प्रतिष्ठा नहीं है ...) आपको सावधान रहने की ज़रूरत है कि आप विभिन्न थ्रेड्स से उपयोग किए जाने वाले deque के कौन से तरीके हैं।

deque.get () थ्रेडसेफ़ प्रतीत होता है, लेकिन मैंने पाया है कि कर रहा है

for item in a_deque:
   process(item)

विफल हो सकता है अगर एक और धागा एक ही समय में आइटम जोड़ रहा है। मुझे एक RuntimeException मिली, जिसने शिकायत की "पुनरावृत्ति के दौरान म्यूट किया गया"।

कौन से ऑपरेशन इससे प्रभावित हैं, यह देखने के लिए collectionmodule.c की जाँच करें


इस तरह की त्रुटि थ्रेड्स और प्रिंसिपल थ्रेड-सेफ्टी के लिए विशेष नहीं है। ऐसा होता है जैसे कि सिर्फ करने से >>> di = {1:None} >>> for x in di: del di[x]
kxr

1
मूल रूप से आपको कभी भी किसी ऐसी चीज पर लूप नहीं करना चाहिए जिसे किसी अन्य धागे द्वारा संशोधित किया जा सकता है (हालांकि कुछ मामलों में आप इसे अपनी सुरक्षा जोड़कर कर सकते हैं)। एक कतार की तरह, आप इसे संसाधित करने से पहले कतार से एक आइटम को पॉप / प्राप्त करने का इरादा रखते हैं, और आप आमतौर पर एक whileलूप के साथ ऐसा करेंगे ।
फंतासी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.