【问题标题】:Python Multiprocessing Pool Stops AbruptlyPython 多处理池突然停止
【发布时间】:2021-01-31 22:52:27
【问题描述】:

我正在尝试根据我的要求执行并行处理,对于 4k-5k 元素并行处理,代码似乎可以正常工作。但是一旦要处理的元素开始增加,代码就会处理一些列表,然后在没有抛出任何错误的情况下,程序突然停止运行。

我检查了一下,程序没有挂起,RAM 可用(我有 16 Gb RAM),CPU 利用率甚至不到 30%。似乎无法弄清楚发生了什么。我有 100 万个元素要处理。

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()

更新 -

发现错误- BrokenPipeError: [Errno 32] Broken pipe

追踪-

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

【问题讨论】:

  • 我希望您在尝试从同一个 api 并行下载 100 万个项目之前检查了他们的请求速率限制。
  • 是的,我有..这就足够了,如果我只是简单地下载所需的项目,它就可以正常工作,但只要我将处理元素引入多处理。然后它开始失败..
  • 尝试使用processes=1 看看它是否仍然失败。 16GB 对于 10 个进程来说并不多,你的操作系统可能已经杀死了一个进程。尝试使用concurrent.futures.ProcessPoolExecutor 而不是multiprocessing.Pool,如果发生这种情况,它将立即中断。还要测试注释掉数据库部分是否有帮助。 print() 是你的朋友,用它来归零它开始挂起的位置。
  • 单个进程不会发生。当 processes=1 事情顺利进行到最后。
  • 您对我下面的回复有任何反馈吗?因为如果您尝试了我的建议并让我知道发生了什么,我可能会有进一步的建议。例如,您是否发现确实有任务挂起但现在超时?或者通过以 1000 的批量提交它运行到完成(据我所知,这是有问题的——如果是这种情况,它应该以更大的批量工作)。

标签: python-3.x python-3.6 python-multiprocessing


【解决方案1】:
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()

【讨论】:

  • 首先,Chunksize 是 map/imap 的 arg,而不是 Pool 的 arg。无论如何,得到了你的建议。但是,它似乎没有任何效果。仍然弹出相同的问题..
  • 我的错!我会编辑那个。我认为这可能是因为您试图插入数据库。可能是数据库无法同时提供许多连接。您能否删除数据库插入/更新步骤并检查代码是否有效。这将有助于缩小问题范围,因为此多处理代码适用于我的用例。
【解决方案2】:

代码看起来正确。我唯一能想到的是你所有的进程都在等待完成。这里有一个建议:不要使用apply_async提供的回调机制,而是使用返回的AsyncResult对象从进程中获取返回值。您可以在此对象上调用get,指定超时值(下面任意指定30 秒,可能不够长)。如果任务在这段时间内没有完成,将抛出一个超时异常(如果你愿意,你可以捕获它)。但这将检验进程挂起的假设。只需确保指定一个足够大的超时值,以使任务应在该时间段内完成。我还将任务提交分成 1000 个批次,不是因为我认为 1,000,000 的大小是一个问题本身,而是因为您没有 1,000,000 个结果对象的列表.但是,如果您发现自己不再因此而挂起,那么请尝试增加批量大小,看看是否会产生影响。

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()

更新

根据谷歌搜索您的管道损坏错误的几个页面,您的错误似乎可能是内存耗尽的结果。例如,请参阅Python Multiprocessing: Broken Pipe exception after increasing Pool size。以下返工尝试使用更少的内存。如果可行,您可以尝试增加批量大小:

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()

【讨论】:

  • 尝试了这种方法,并指定了 30 秒的超时时间(单个处理通常需要大约 2 秒。)在大约 5k 次迭代后,其中一个任务(随机)导致超时异常。
  • @RajatSuneja 这表明它挂在什么东西上。如果您在对result.get(TIMEOUT_VALUE) 的调用周围加上try/catch,您将继续处理并查看所有挂起的进程。你必须弄清楚他们在坚持什么。但可以肯定的是,这是批量大小为 1000?您还可以将TIMEOUT_VALUE 减少到一个较小的值,这样您就不会等待太长时间来获取超时异常。如果你说一个进程应该在 2 秒内完成,那么如果它没有在 5 秒或 10 秒之内完成,你可以确定它已经挂起。
  • 是的,建议的批量大小为 1k。
  • 您可能必须修改start_processing 以记录其进度,因为它继续执行其逻辑以确定挂起的位置。见How should I log while using multiprocessing in Python?Logging to a single file from multiple processes
  • 您还可以让每个进程在已知目录中以附加模式打开一个唯一命名的输出文件,并将调试信息写入每次写入后关闭的文件。当该过程完全完成时,它会删除该文件。挂起的进程将有未删除的文件,您可以对其进行检查——这是一种技术含量非常低的解决方案。
【解决方案3】:

我在 Linux 上使用 Python 3.8 有相同的体验。我用Python 3.7 设置了一个新环境,multiprocessing.Pool() 现在可以正常工作了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-13
    • 2020-09-21
    • 2021-07-24
    • 2020-08-14
    • 2016-11-10
    • 1970-01-01
    相关资源
    最近更新 更多