Каковы основные различия между очередями и трудами в Пакет многопроцессорности Python?
В каких сценариях следует выбирать один за другим? Когда выгодно использовать Pipe()
? Когда выгодно использовать Queue()
?
Каковы основные различия между очередями и трудами в Пакет многопроцессорности Python?
В каких сценариях следует выбирать один за другим? Когда выгодно использовать Pipe()
? Когда выгодно использовать Queue()
?
Pipe()
может иметь только две конечные точки.
Queue()
может иметь несколько производителей и потребителей.
Когда их использовать
Если вам нужно больше двух точек для общения, используйте Queue()
.
Если вам нужна абсолютная производительность, Pipe()
намного быстрее, потому что Queue()
построен поверх Pipe()
.
Сравнительный анализ производительности
Предположим, вы хотите порождать два процесса и посылать сообщения между ними как можно быстрее. Это временные результаты гонки сопротивления между аналогичными тестами с использованием Pipe()
и Queue()
... Это на ThinkpadT61 с Ubuntu 11.10 и Python 2.7.2.
К вашему сведению, я добавил результаты для JoinableQueue()
в качестве бонуса; JoinableQueue()
учитывает задачи, когда вызывается queue.task_done()
(он даже не знает о конкретной задаче, он просто считает незавершенные задачи в очереди), поэтому queue.join()
знает, что работа завершена.
Код для каждого в нижней части этого ответа...
[email protected]:~$ 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
[email protected]:~$ 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
[email protected]:~$ 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
[email protected]:~$
В итоге Pipe()
примерно в три раза быстрее, чем Queue()
. Даже не думайте о JoinableQueue()
, если вам не нужны преимущества.
БОНУСНЫЙ МАТЕРИАЛ 2
Многопроцессорная обработка вносит незначительные изменения в поток информации, которые затрудняют отладку, если вы не знаете некоторых ярлыков. Например, у вас может быть сценарий, который отлично работает при индексировании в словаре при многих условиях, но редко дает сбой при определенных входных данных.
Обычно мы получаем подсказки о сбое, когда происходит сбой всего процесса Python; однако вы не получите незапрошенные трассировочные сообщения о сбоях, напечатанные на консоли, если происходит сбой многопроцессорной функции. Отслеживать неизвестные многопроцессорные сбои сложно, даже не зная, что привело к сбою процесса.
Самый простой способ, который я нашел для отслеживания информации о сбое многопроцессорной обработки, - это обернуть всю многопроцессорную функцию в try
/except
и использовать traceback.print_exc()
:
import traceback
def reader(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(task_q, result_q)
File "foo.py", line 46, in run
raise ValueError
ValueError
Исходный код:
"""
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)))