Çoklu İşleme - Boru ve Sıra


Yanıtlar:


286
  • A'nın Pipe()yalnızca iki uç noktası olabilir.

  • A Queue()birden fazla üreticiye ve tüketiciye sahip olabilir.

Ne zaman kullanılmalı

İletişim kurmak için ikiden fazla noktaya ihtiyacınız varsa, a Queue().

Mutlak performansa ihtiyacınız varsa, a Pipe()çok daha hızlıdır çünkü Queue()üzerine inşa edilmiştir Pipe().

Performans Kıyaslaması

Mümkün olduğu kadar çabuk iki süreç oluşturmak ve aralarında mesaj göndermek istediğinizi varsayalım. Bunlar, Pipe()ve Queue()... kullanan benzer testler arasındaki sürükleme yarışının zamanlama sonuçlarıdır. Bu, Ubuntu 11.10 ve Python 2.7.2 çalıştıran bir ThinkpadT61 üzerindedir.

Bilginize, JoinableQueue()bonus olarak sonuçlara attım ; çağrıldığında JoinableQueue()görevleri hesaba queue.task_done()katar (belirli bir görevi bile bilmez, yalnızca kuyruktaki bitmemiş görevleri sayar), böylece queue.join()işin bittiğini bilir.

Bu cevabın altındaki her birinin kodu ...

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:~$

Özetle Pipe(), a'dan yaklaşık üç kat daha hızlıdır Queue(). JoinableQueue()Gerçekten avantajlara sahip olmadığınız sürece bunu düşünmeyin bile .

BONUS MATERYAL 2

Çoklu işlem, bilgi akışında bazı kısayolları bilmediğiniz sürece hata ayıklamayı zorlaştıran ince değişiklikler getirir. Örneğin, birçok koşulda bir sözlük aracılığıyla indeksleme yaparken iyi çalışan, ancak belirli girdilerde nadiren başarısız olan bir komut dosyanız olabilir.

Normalde tüm python işlemi çöktüğünde başarısızlığa dair ipuçları elde ederiz; ancak, çoklu işlem işlevi çökerse, konsola yazdırılan istenmeyen kilitlenme izleri almazsınız. Bilinmeyen çoklu işlem çökmelerini izlemek, süreci neyin çökerttiğine dair bir ipucu olmadan zordur.

Çoklu işlem çökme bilgilerini bulmanın bulduğum en basit yolu, tüm çoklu işlem işlevini bir try/ exceptve kullanmaktır 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()

Şimdi, bir çökme bulduğunuzda şöyle bir şey görürsünüz:

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'

Kaynak kodu:


"""
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
@Jonathan "Özet olarak Pipe (), Kuyruktan () yaklaşık üç kat daha hızlıdır"
James Brady

13
Mükemmel! İyi yanıt ve kıyaslama sağlamanız güzel! Sadece iki küçük kelime bilgim var: (1) "büyüklük sıraları daha hızlı" biraz abartılı bir ifade. Aradaki fark x3'dür ve bu, bir büyüklük derecesinin yaklaşık üçte biri kadardır. Sadece söylüyorum. ;-); ve (2) daha adil bir karşılaştırma, hepsi tek bir noktadan çok noktaya kuyruktan çekim yapan N sayıda çalışanı çalıştırma performansına kıyasla, her biri ana iş parçacığı ile noktadan noktaya boru aracılığıyla iletişim kuran N sayıda çalışan çalıştırma olacaktır.
JJC

3
"Bonus Malzemenize" ... Evet. Süreci alt sınıflara ayırıyorsanız, 'çalıştır' yönteminin büyük bir kısmını bir try bloğuna koyun. Bu aynı zamanda istisnaların günlüğe kaydedilmesi için de yararlı bir yoldur. Normal istisna çıktısını çoğaltmak için: sys.stderr.write (''. Join (traceback.format_exception (* (sys.exc_info ()))))
2013

2
@ alexpinho98 - ancak gönderdiğiniz şeyin normal veri değil, hata verisi olduğunu belirtmek için bazı bant dışı verilere ve ilişkili sinyalleme moduna ihtiyacınız olacak. başlangıç ​​sürecinin zaten tahmin edilemeyen bir durumda olduğunu görmek, bu sorulamayacak kadar fazla olabilir.
scytale

10
@JJC Kelime tartışmanızla kelime oyunu yapmak için, 3x üçte bir değil, yaklaşık yarısı kadardır - sqrt (10) = ~ 3.
jab

3

Bunun Queue()kayda değer bir başka özelliği de besleyici ipliğidir. Bu bölümde "Bir işlem bir öğeyi sıraya ilk kez koyduğunda, nesneleri bir tampondan boruya aktaran bir besleyici iş parçacığı başlatılır." Sonsuz sayıda (veya maksimum boyutta) öğe, Queue()herhangi bir queue.put()engelleme çağrısı olmadan eklenebilir . Bu, Queue()programınız bunları işlemeye hazır olana kadar, birden çok öğeyi bir içinde saklamanıza olanak tanır .

Pipe()diğer yandan, bir bağlantıya gönderilmiş ancak diğer bağlantıdan alınmamış öğeler için sınırlı miktarda depolama alanına sahiptir. Bu depolama kullanıldıktan sonra, yapılacak çağrılar, connection.send()öğenin tamamını yazmak için alan kalana kadar engellenecektir. Bu, başka bir iş parçacığı borudan okuyana kadar iş parçacığının yazmasını geciktirir. Connectionnesneler, temeldeki dosya tanımlayıcısına erişmenizi sağlar. * Nix sistemlerinde, işlevi connection.send()kullanarak çağrıların engellenmesini önleyebilirsiniz os.set_blocking(). Ancak, borunun dosyasına sığmayan tek bir öğe göndermeye çalışırsanız bu sorunlara neden olacaktır. Linux'un son sürümleri, bir dosyanın boyutunu artırmanıza izin verir, ancak izin verilen maksimum boyut, sistem yapılandırmalarına bağlı olarak değişir. Bu nedenle asla Pipe()arabelleğe alınan verilere güvenmemelisiniz . Çağrılarconnection.send veriler başka bir yerde borudan okunana kadar engellenebilir.

Sonuç olarak Kuyruk, verileri arabelleğe almanız gerektiğinde kanaldan daha iyi bir seçimdir. Sadece iki nokta arasında iletişim kurmanız gerektiğinde bile.


Bağlamak bölüm besleyici iplik hakkında bir not yapar, ama dokümantasyon ait putyönteme hala engelleme ya da başarısız yöntem ilan etmesi: "İsteğe bağlı argüman bloğu (varsayılan) True ve zaman aşımı Yok (varsayılan), blok halinde ise boş bir yuva mevcut olana kadar gereklidir. Zaman aşımı pozitif bir sayı ise, çoğu zaman aşımı saniyesini engeller ve kuyruğu yükseltir. Bu süre içinde boş yuva yoksa tam istisna. " Cevabınızdan emin misiniz?
Anab

Cevabımdan eminim. putUsul, engeller maxsizekurucusuna parametre Queuebelirtilir. Ancak bunun nedeni, tek tek öğelerin boyutu değil, sıradaki öğe sayısı olacaktır.
Roger Iyengar

Açıklama için teşekkürler, o kısmı kaçırmıştım.
Anab
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.