Pool de multiprocessamento Python para abruptamente
Estou tentando realizar o processamento paralelo para meus requisitos e o código parece estar funcionando conforme o esperado para elementos 4k-5k em paralelo. Mas, assim que os elementos a serem processados começam a aumentar, o código processa algumas listagens e, em seguida, sem gerar nenhum erro, o programa para de funcionar abruptamente.
Verifiquei e o programa não travou, a RAM está disponível (tenho 16 Gb de RAM) e a utilização da CPU não chega a 30%. Não consigo descobrir o que está acontecendo. Tenho 1 milhão de elementos a serem processados.
def get_items_to_download():
#iterator to fetch all items that are to be downloaded
yield download_item
def start_download_process():
multiproc_pool = multiprocessing.Pool(processes=10)
for download_item in get_items_to_download():
multiproc_pool.apply_async(start_processing, args = (download_item, ), callback = results_callback)
multiproc_pool.close()
multiproc_pool.join()
def start_processing(download_item):
try:
# Code to download item from web API
# Code to perform some processing on the data
# Code to update data into database
return True
except Exception as e:
return False
def results_callback(result):
print(result)
if __name__ == "__main__":
start_download_process()
ATUALIZAR -
Encontrado o erro - BrokenPipeError: [Errno 32] Tubo quebrado
Vestígio -
Traceback (most recent call last):
File "/usr/lib/python3.6/multiprocessing/pool.py", line 125, in worker
put((job, i, result))
File "/usr/lib/python3.6/multiprocessing/queues.py", line 347, in put
self._writer.send_bytes(obj)
File "/usr/lib/python3.6/multiprocessing/connection.py", line 200, in send_bytes
self._send_bytes(m[offset:offset + size])
File "/usr/lib/python3.6/multiprocessing/connection.py", line 404, in _send_bytes
self._send(header + buf)
File "/usr/lib/python3.6/multiprocessing/connection.py", line 368, in _send
n = write(self._handle, buf)
BrokenPipeError: [Errno 32] Broken pipe
Respostas
def get_items_to_download():
#instead of yield, return the complete generator object to avoid iterating over this function.
#Return type - generator (download_item1, download_item2...)
return download_item
def start_download_process():
download_item = get_items_to_download()
# specify the chunksize to get faster results.
with multiprocessing.Pool(processes=10) as pool:
#map_async() is also available, if that's your use case.
results= pool.map(start_processing, download_item, chunksize=XX )
print(results)
return(results)
def start_processing(download_item):
try:
# Code to download item from web API
# Code to perform some processing on the data
# Code to update data into database
return True
except Exception as e:
return False
def results_callback(result):
print(result)
if __name__ == "__main__":
start_download_process()
O código parece correto. A única coisa que consigo pensar é que todos os seus processos estão suspensos, aguardando a conclusão. Aqui está uma sugestão: em vez de usar o mecanismo de retorno de chamada fornecido por apply_async, use o AsyncResultobjeto que é retornado para obter o valor de retorno do processo. Você pode ligargetneste objeto especificando um valor de tempo limite (30 segundos especificados arbitrariamente abaixo, possivelmente não longo o suficiente). Se a tarefa não foi concluída nessa duração, uma exceção de tempo limite será lançada (você pode pegá-la, se desejar). Mas isso testará a hipótese de que os processos estão travando. Apenas certifique-se de especificar um valor de tempo limite que seja suficientemente grande para que a tarefa seja concluída nesse período de tempo. Também dividi os envios de tarefas em lotes de 1.000, não porque acho que o tamanho de 1.000.000 seja um problema em si , mas apenas para que você não tenha uma lista de 1.000.000 de objetos de resultado. Mas se você descobrir que não está mais travando, tente aumentar o tamanho do lote e veja se isso faz diferença.
import multiprocessing
def get_items_to_download():
#iterator to fetch all items that are to be downloaded
yield download_item
BATCH_SIZE = 1000
def start_download_process():
with multiprocessing.Pool(processes=10) as multiproc_pool:
results = []
for download_item in get_items_to_download():
results.append(multiproc_pool.apply_async(start_processing, args = (download_item, )))
if len(results) == BATCH_SIZE:
process_results(results)
results = []
if len(results):
process_results(results)
def start_processing(download_item):
try:
# Code to download item from web API
# Code to perform some processing on the data
# Code to update data into database
return True
except Exception as e:
return False
TIMEOUT_VALUE = 30 # or some suitable value
def process_results(results):
for result in results:
return_value = result.get(TIMEOUT_VALUE) # will cause an exception if process is hanging
print(return_value)
if __name__ == "__main__":
start_download_process()
Atualizar
Com base na pesquisa de várias páginas no Google sobre o erro do tubo quebrado, parece que o erro pode ser o resultado do esgotamento da memória. Consulte Multiprocessamento Python: exceção de tubo quebrado após aumentar o tamanho do pool , por exemplo. O retrabalho a seguir tenta utilizar menos memória. Se funcionar, você pode tentar aumentar o tamanho do lote:
import multiprocessing
BATCH_SIZE = 1000
POOL_SIZE = 10
def get_items_to_download():
#iterator to fetch all items that are to be downloaded
yield download_item
def start_download_process():
with multiprocessing.Pool(processes=POOL_SIZE) as multiproc_pool:
items = []
for download_item in get_items_to_download():
items.append(download_item)
if len(items) == BATCH_SIZE:
process_items(multiproc_pool, items)
items = []
if len(items):
process_items(multiproc_pool, items)
def start_processing(download_item):
try:
# Code to download item from web API
# Code to perform some processing on the data
# Code to update data into database
return True
except Exception as e:
return False
def compute_chunksize(iterable_size):
if iterable_size == 0:
return 0
chunksize, extra = divmod(iterable_size, POOL_SIZE * 4)
if extra:
chunksize += 1
return chunksize
def process_items(multiproc_pool, items):
chunksize = compute_chunksize(len(items))
# you must iterate the iterable returned:
for return_value in multiproc_pool.imap(start_processing, items, chunksize):
print(return_value)
if __name__ == "__main__":
start_download_process()