Đa xử lý - Ống vs Hàng đợi


Câu trả lời:


281
  • A Pipe()chỉ có thể có hai điểm cuối.

  • A Queue()có thể có nhiều nhà sản xuất và người tiêu dùng.

Khi nào sử dụng chúng

Nếu bạn cần nhiều hơn hai điểm để giao tiếp, hãy sử dụng a Queue().

Nếu bạn cần hiệu suất tuyệt đối, Pipe()nhanh hơn nhiều vì Queue()được xây dựng trên đầu trang Pipe().

Điểm chuẩn hiệu suất

Giả sử bạn muốn sinh ra hai quy trình và gửi tin nhắn giữa chúng càng nhanh càng tốt. Đây là kết quả thời gian của một cuộc đua kéo giữa các thử nghiệm tương tự bằng cách sử dụng Pipe()Queue()... Đây là trên ThinkpadT61 chạy Ubuntu 11.10 và Python 2.7.2.

FYI, tôi đã ném kết quả JoinableQueue()như một phần thưởng; JoinableQueue()tài khoản cho các nhiệm vụ khi queue.task_done()được gọi (thậm chí không biết về nhiệm vụ cụ thể, nó chỉ tính các nhiệm vụ chưa hoàn thành trong hàng đợi), để queue.join()biết rằng công việc đã kết thúc.

Mã cho mỗi ở dưới cùng của câu trả lời này ...

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

Tóm lại Pipe()là nhanh hơn khoảng ba lần so với a Queue(). Thậm chí đừng nghĩ về việc JoinableQueue()trừ khi bạn thực sự phải có lợi ích.

TIỀN THƯỞNG 2

Đa xử lý giới thiệu những thay đổi tinh tế trong luồng thông tin khiến việc gỡ lỗi khó khăn trừ khi bạn biết một số phím tắt. Chẳng hạn, bạn có thể có một tập lệnh hoạt động tốt khi lập chỉ mục qua từ điển trong nhiều điều kiện, nhưng không thường xuyên bị lỗi với một số đầu vào nhất định.

Thông thường chúng ta có được manh mối cho sự thất bại khi toàn bộ quá trình trăn bị hỏng; tuy nhiên, bạn không nhận được các dấu vết sự cố không mong muốn được in ra bàn điều khiển nếu chức năng đa xử lý gặp sự cố. Theo dõi các sự cố đa xử lý không xác định là khó khăn mà không có manh mối về những gì đã làm hỏng quá trình.

Cách đơn giản nhất mà tôi đã tìm thấy để theo dõi thông tin sự cố đa xử lý là bọc toàn bộ chức năng đa xử lý trong một try/ exceptvà sử dụng 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()

Bây giờ, khi bạn tìm thấy một vụ tai nạn, bạn thấy một cái gì đó như:

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'

Mã nguồn:


"""
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 "Tóm lại ống () nhanh hơn khoảng ba lần so với Hàng đợi ()"
James Brady

13
Thông minh! Câu trả lời tốt và tốt đẹp mà bạn cung cấp điểm chuẩn! Tôi chỉ có hai ngụy biện nhỏ: (1) "mệnh lệnh cường độ nhanh hơn" là một chút quá lời. Sự khác biệt là x3, tức là khoảng một phần ba của một độ lớn. Chỉ cần nói. ;-); và (2) một so sánh công bằng hơn sẽ là chạy N worker, mỗi giao tiếp với luồng chính qua đường ống điểm-điểm so với hiệu suất của N worker đang chạy đều kéo từ một hàng đợi đa điểm.
JJC

3
Để "Tài liệu khen thưởng" của bạn ... Vâng. Nếu bạn đang phân lớp Quá trình, hãy đặt phần lớn phương thức 'chạy' vào một khối thử. Đó cũng là một cách hữu ích để ghi nhật ký các trường hợp ngoại lệ. Để sao chép đầu ra ngoại lệ bình thường: sys.stderr.write (''. Tham gia (tracBack.format_exception (* (sys.exc_info ()))))
travc

2
@ alexpinho98 - nhưng bạn sẽ cần một số dữ liệu ngoài băng tần và chế độ báo hiệu liên quan, để chỉ ra rằng những gì bạn đang gửi không phải là dữ liệu thông thường mà là dữ liệu lỗi. xem như quá trình khởi tạo đã ở trong một trạng thái không thể đoán trước, điều này có thể là quá nhiều để hỏi.
scytale

10
@JJC Để ngụy biện với khả năng phân minh của bạn, 3x là khoảng một nửa bậc độ lớn, không phải là một phần ba - sqrt (10) = ~ 3.
jab

0

Một tính năng bổ sung Queue()đáng chú ý là luồng trung chuyển. Đây phần ghi chú "Khi một quá trình puts đầu tiên một mục trên hàng đợi a thread nạp được bắt đầu mà chuyển đối tượng từ một bộ đệm vào ống." Một số lượng vô hạn các mục (hoặc tối đa hóa) có thể được chèn vào Queue()mà không có bất kỳ lệnh gọi nào để queue.put()chặn. Điều này cho phép bạn lưu trữ nhiều mục trong một Queue(), cho đến khi chương trình của bạn sẵn sàng xử lý chúng.

Pipe()mặt khác, có dung lượng lưu trữ hữu hạn cho các mục đã được gửi đến một kết nối, nhưng chưa được nhận từ kết nối khác. Sau khi lưu trữ này được sử dụng hết, các lệnh gọi connection.send()sẽ chặn cho đến khi có không gian để ghi toàn bộ mục. Điều này sẽ đình trệ các luồng làm việc viết cho đến khi một số luồng khác đọc từ đường ống. Connectioncác đối tượng cung cấp cho bạn quyền truy cập vào bộ mô tả tập tin cơ bản. Trên các hệ thống * nix, bạn có thể ngăn chặn connection.send()các cuộc gọi chặn bằng cách sử dụng os.set_blocking()chức năng. Tuy nhiên, điều này sẽ gây ra sự cố nếu bạn cố gửi một mục không phù hợp với tệp của đường ống. Các phiên bản gần đây của Linux cho phép bạn tăng kích thước tệp, nhưng kích thước tối đa được phép thay đổi dựa trên cấu hình hệ thống. Do đó, bạn không bao giờ nên dựa vào Pipe()dữ liệu đệm. Gọi đếnconnection.send có thể chặn cho đến khi dữ liệu được đọc từ đường ống này.

Tóm lại, Queue là lựa chọn tốt hơn ống khi bạn cần đệm dữ liệu. Ngay cả khi bạn chỉ cần giao tiếp giữa hai điểm.

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.