मल्टीप्रोसेसिंग - पाइप बनाम कतार


151

पायथन के मल्टीप्रोसेसिंग पैकेज में कतारों और पाइपों के बीच बुनियादी अंतर क्या हैं ?

किन परिदृश्यों में एक को दूसरे पर चुनना चाहिए? कब इस्तेमाल करना फायदेमंद है Pipe()? कब इस्तेमाल करना फायदेमंद है Queue()?

जवाबों:


281
  • A में Pipe()केवल दो समापन बिंदु हो सकते हैं।

  • A Queue()में कई उत्पादक और उपभोक्ता हो सकते हैं।

उनका उपयोग कब करना है

यदि आपको संवाद करने के लिए दो से अधिक बिंदुओं की आवश्यकता है, तो a का उपयोग करें Queue()

यदि आपको पूर्ण प्रदर्शन की आवश्यकता है, तो एक Pipe()बहुत तेज़ है क्योंकि Queue()शीर्ष पर बनाया गया है Pipe()

प्रदर्शन बेंचमार्किंग

मान लें कि आप दो प्रक्रियाओं को स्पॉन करना चाहते हैं और जितनी जल्दी हो सके उन दोनों के बीच संदेश भेजना चाहते हैं। ये इसी तरह के परीक्षणों के बीच ड्रैग रेस के समय के परिणाम हैं Pipe()और Queue()... यह एक थिंकपैड T61 पर है जो Ubuntu 11.10 और पायथन 2.7.2 पर चल रहा है।

FYI करें, मैंने JoinableQueue()एक बोनस के रूप में परिणामों में फेंक दिया ; JoinableQueue()जब queue.task_done()कहा जाता है कार्यों के लिए खातों (यह विशिष्ट कार्य के बारे में भी नहीं जानता है, यह सिर्फ कतार में अधूरे कार्यों को गिना जाता है), ताकि पता queue.join()चले कि काम समाप्त हो गया है।

इस उत्तर के नीचे प्रत्येक के लिए कोड ...

mpenning@mpenning-T61:~$ python multi_pipe.py 
Sending 10000 numbers to Pipe() took 0.0369849205017 seconds
Sending 100000 numbers to Pipe() took 0.328398942947 seconds
Sending 1000000 numbers to Pipe() took 3.17266988754 seconds
mpenning@mpenning-T61:~$ python multi_queue.py 
Sending 10000 numbers to Queue() took 0.105256080627 seconds
Sending 100000 numbers to Queue() took 0.980564117432 seconds
Sending 1000000 numbers to Queue() took 10.1611330509 seconds
mpnening@mpenning-T61:~$ python multi_joinablequeue.py 
Sending 10000 numbers to JoinableQueue() took 0.172781944275 seconds
Sending 100000 numbers to JoinableQueue() took 1.5714070797 seconds
Sending 1000000 numbers to JoinableQueue() took 15.8527247906 seconds
mpenning@mpenning-T61:~$

संक्षेप Pipe()में, ए की तुलना में लगभग तीन गुना तेज है Queue()JoinableQueue()जब तक आप वास्तव में लाभ होना चाहिए के बारे में भी मत सोचो ।

बोनस सामग्री 2

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

आम तौर पर हम विफलता के लिए सुराग प्राप्त करते हैं जब पूरी अजगर प्रक्रिया दुर्घटनाग्रस्त हो जाती है; हालाँकि, यदि आप मल्टीप्रोसेसिंग फंक्शन क्रैश करते हैं, तो आपको कंसोल पर छपे अनचाही क्रैश ट्रेसबैक नहीं मिलते हैं। अज्ञात मल्टीप्रोसेसिंग क्रैश को ट्रैक करना इस प्रक्रिया के क्रैश होने के सुराग के बिना कठिन है।

मल्टीप्रोसेसिंग क्रैश सूचना ट्रैक को ट्रैक करने के लिए मैंने जो सबसे सरल तरीका पाया है, वह यह है कि संपूर्ण मल्टीप्रोसेसिंग फ़ंक्शन को try/ exceptऔर में उपयोग करें traceback.print_exc():

import traceback
def run(self, args):
    try:
        # Insert stuff to be multiprocessed here
        return args[0]['that']
    except:
        print "FATAL: reader({0}) exited while multiprocessing".format(args) 
        traceback.print_exc()

अब, जब आप एक दुर्घटना पाते हैं तो आप कुछ इस तरह देखते हैं:

FATAL: reader([{'crash': 'this'}]) exited while multiprocessing
Traceback (most recent call last):
  File "foo.py", line 19, in __init__
    self.run(args)
  File "foo.py", line 46, in run
    KeyError: 'that'

सोर्स कोड:


"""
multi_pipe.py
"""
from multiprocessing import Process, Pipe
import time

def reader_proc(pipe):
    ## Read from the pipe; this will be spawned as a separate Process
    p_output, p_input = pipe
    p_input.close()    # We are only reading
    while True:
        msg = p_output.recv()    # Read from the output pipe and do nothing
        if msg=='DONE':
            break

def writer(count, p_input):
    for ii in xrange(0, count):
        p_input.send(ii)             # Write 'count' numbers into the input pipe
    p_input.send('DONE')

if __name__=='__main__':
    for count in [10**4, 10**5, 10**6]:
        # Pipes are unidirectional with two endpoints:  p_input ------> p_output
        p_output, p_input = Pipe()  # writer() writes to p_input from _this_ process
        reader_p = Process(target=reader_proc, args=((p_output, p_input),))
        reader_p.daemon = True
        reader_p.start()     # Launch the reader process

        p_output.close()       # We no longer need this part of the Pipe()
        _start = time.time()
        writer(count, p_input) # Send a lot of stuff to reader_proc()
        p_input.close()
        reader_p.join()
        print("Sending {0} numbers to Pipe() took {1} seconds".format(count,
            (time.time() - _start)))

"""
multi_queue.py
"""

from multiprocessing import Process, Queue
import time
import sys

def reader_proc(queue):
    ## Read from the queue; this will be spawned as a separate Process
    while True:
        msg = queue.get()         # Read from the queue and do nothing
        if (msg == 'DONE'):
            break

def writer(count, queue):
    ## Write to the queue
    for ii in range(0, count):
        queue.put(ii)             # Write 'count' numbers into the queue
    queue.put('DONE')

if __name__=='__main__':
    pqueue = Queue() # writer() writes to pqueue from _this_ process
    for count in [10**4, 10**5, 10**6]:             
        ### reader_proc() reads from pqueue as a separate process
        reader_p = Process(target=reader_proc, args=((pqueue),))
        reader_p.daemon = True
        reader_p.start()        # Launch reader_proc() as a separate python process

        _start = time.time()
        writer(count, pqueue)    # Send a lot of stuff to reader()
        reader_p.join()         # Wait for the reader to finish
        print("Sending {0} numbers to Queue() took {1} seconds".format(count, 
            (time.time() - _start)))

"""
multi_joinablequeue.py
"""
from multiprocessing import Process, JoinableQueue
import time

def reader_proc(queue):
    ## Read from the queue; this will be spawned as a separate Process
    while True:
        msg = queue.get()         # Read from the queue and do nothing
        queue.task_done()

def writer(count, queue):
    for ii in xrange(0, count):
        queue.put(ii)             # Write 'count' numbers into the queue

if __name__=='__main__':
    for count in [10**4, 10**5, 10**6]:
        jqueue = JoinableQueue() # writer() writes to jqueue from _this_ process
        # reader_proc() reads from jqueue as a different process...
        reader_p = Process(target=reader_proc, args=((jqueue),))
        reader_p.daemon = True
        reader_p.start()     # Launch the reader process
        _start = time.time()
        writer(count, jqueue) # Send a lot of stuff to reader_proc() (in different process)
        jqueue.join()         # Wait for the reader to finish
        print("Sending {0} numbers to JoinableQueue() took {1} seconds".format(count, 
            (time.time() - _start)))

2
@ जोनाथन "सारांश पाइप में () एक क्यू ()" की तुलना में लगभग तीन गुना तेज है
जेम्स ब्रैडी

13
अति उत्कृष्ट! अच्छा जवाब और अच्छा है कि आपने बेंचमार्क प्रदान किया! मेरे पास केवल दो छोटे क्विबल्स हैं: (1) "परिमाण के आदेश तेजी से" एक ओवरस्टेट का एक सा है। अंतर x3 है, जो परिमाण के एक आदेश के एक तिहाई के बारे में है। बस केह रहा हू। ;-); और (2) एक अधिक निष्पक्ष तुलना एन श्रमिकों को चलाने वाली होगी, जिनमें से प्रत्येक मुख्य बिंदु से पॉइंट थ्रू पाइप के माध्यम से संचार करेगा, जो एन वर्कर्स के चलने के प्रदर्शन की तुलना में सिंगल पॉइंट-टू-मल्टीपॉइंट कतार से खींच रहा है।
जेजेसी

3
अपने "बोनस सामग्री" के लिए ... हाँ। यदि आप उप-प्रक्रिया कर रहे हैं, तो एक कोशिश ब्लॉक में 'रन' विधि का थोक डालें। यह अपवादों को लॉग करने का एक उपयोगी तरीका भी है। सामान्य अपवाद आउटपुट को दोहराने के लिए: sys.stderr.write (''। Join (ट्रेसबैक.फॉर्मैट_एक्ससेप्शन (* (sys.exc_info ())))
travc

2
@ alexpinho98 - लेकिन आपको कुछ आउट-ऑफ-बैंड डेटा और संबद्ध सिग्नलिंग मोड की आवश्यकता है, यह इंगित करने के लिए कि आप जो भेज रहे हैं वह नियमित डेटा नहीं है लेकिन त्रुटि डेटा है। मूल प्रक्रिया के रूप में देखना अप्रत्याशित स्थिति में पहले से ही है, यह पूछने के लिए बहुत अधिक हो सकता है।
स्काइटेल

10
@JJC अपने वक्रोक्ति के साथ वशीकरण करने के लिए, 3x एक परिमाण का आधा क्रम है, न कि तीसरा - sqrt (10) = ~ 3.
jab

1

एक अतिरिक्त विशेषता Queue()यह ध्यान देने योग्य है कि फीडर थ्रेड है। यह खंड नोट करता है "जब एक प्रक्रिया पहली बार एक आइटम को एक फीडर थ्रेड पर डालती है, जो एक बफर से वस्तुओं को पाइप में स्थानांतरित करती है।" अवरुद्ध Queue()करने के लिए किसी भी कॉल के बिना (या अधिकतम) आइटम की एक अनंत संख्या डाली जा सकती है queue.put()। यह आपको कई वस्तुओं को स्टोर करने की अनुमति देता है Queue(), जब तक कि आपका कार्यक्रम उन्हें संसाधित करने के लिए तैयार नहीं है।

Pipe()दूसरी ओर, उन वस्तुओं के भंडारण की एक सीमित मात्रा है जो एक कनेक्शन के लिए भेजी गई हैं, लेकिन दूसरे कनेक्शन से प्राप्त नहीं हुई हैं। इस संग्रहण का उपयोग किए जाने के बाद, connection.send()संपूर्ण आइटम को लिखने के लिए स्थान होने तक कॉल ब्लॉक हो जाएगी। यह लेखन करने वाले धागे को तब तक स्टाल करेगा जब तक कि पाइप से कुछ अन्य धागा नहीं पढ़ता। Connectionऑब्जेक्ट आपको अंतर्निहित फ़ाइल डिस्क्रिप्टर तक पहुँच प्रदान करते हैं। * निक्स सिस्टम पर, आप फ़ंक्शन connection.send()का उपयोग करके कॉल को रोकने से रोक सकते हैं os.set_blocking()। हालांकि, यह समस्या पैदा करेगा यदि आप किसी एकल आइटम को भेजने का प्रयास करते हैं जो पाइप की फ़ाइल में फिट नहीं होता है। लिनक्स के हाल के संस्करण आपको एक फ़ाइल का आकार बढ़ाने की अनुमति देते हैं, लेकिन सिस्टम कॉन्फ़िगरेशन के आधार पर अनुमत अधिकतम आकार भिन्न होता है। इसलिए आपको कभी भी Pipe()बफर डेटा पर भरोसा नहीं करना चाहिए । को कॉल करता हैconnection.send ब्लॉक को तब तक रोक सकता है जब तक कि पाइप से डेटा किसी और जगह से न पढ़े।

निष्कर्ष में, क्यू आपको पाइप से बेहतर विकल्प है जब आपको डेटा को बफर करने की आवश्यकता होती है। यहां तक ​​कि जब आपको केवल दो बिंदुओं के बीच संवाद करने की आवश्यकता होती है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.