通过定期调用`join()`来避免僵尸进程

时间:2017-03-31 01:59:10

标签: python multiprocessing

我正在用Python编写一个程序,它永远运行并随机接收请求 必须并行处理。每个请求可能需要几十分钟 处理并给CPU带来一些负担,因此asyncio不是一个选项。对于 每个请求我都会启动一个新的工作进程。

问题是,如果我在工作完成后没有给join()打电话, 它变成了一个僵尸进程。

我目前的解决方案是定期迭代所有工作进程并调用 join()如果他们完成了multiprocessing.Queue.get()。有没有比使用a更优雅的方式 #!/usr/bin/env python3 import multiprocessing as mp import queue import random import time from typing import List def main(): q = mp.Queue() p_produce = mp.Process(target=produce, args=(q,)) p_receive = mp.Process(target=receive, args=(q,)) p_produce.start() p_receive.start() p_receive.join() p_produce.join() def produce(q: mp.Queue): for i in range(10): print(f"put({i})") q.put(str(i)) time.sleep(random.uniform(2.0, 3.0)) q.put("EOF") def receive(q: mp.Queue): workers = [] # type: List[mp.Process] while True: to_join = [w for w in workers if not w.is_alive()] for p_worker in to_join: print(f"Join {p_worker.name}") p_worker.join() workers.remove(p_worker) try: request = q.get(block=True, timeout=1) # Is there a better way? except queue.Empty: continue if request == "EOF": break p_worker = mp.Process(target=worker, args=(request,), name=request) p_worker.start() workers.append(p_worker) for p_worker in workers: print(f"Join {p_worker.name}") p_worker.join() def worker(name: str): print(f"Working on {name}") time.sleep(random.uniform(2.0, 3.0)) if __name__ == "__main__": main() 超时?也许是事件驱动的方法?或者在这种情况下完全使用超时? 请参阅以下代码 目前的解决方案。

<form action='login.php' method='post'>
  <input type='text' name='username'>
  <input type='password' name='password'>
  <input type='submit' value='Login'>
</form>

2 个答案:

答案 0 :(得分:3)

正如@Giannis在评论中建议的那样,您从头开始重新创建流程管理员。坚持使用Python提供的功能,您是否反对使用multiprocessing.Pool?如果是这样,是什么?

执行此操作的常用方法是选择要同时运行的最大工作进程数。说,

NUM_WORKERS = 4

然后将其删除以替换receive()功能:

def receive(q: mp.Queue):
    pool = mp.Pool(NUM_WORKERS)
    while True:
        request = q.get()
        if request == "EOF":
            break
        pool.apply_async(worker, args=(request,))
    pool.close()
    pool.join()

NUM_WORKERS进程只创建一次,并在任务中重用。如果出于某种原因,您需要(或想要)为每项任务创建一个全新的流程,则只需将maxtasksperchild=1添加到Pool构造函数中。

如果出于某种原因你需要知道每个任务何时完成,你可以,例如,在callback=调用中添加一个apply_async()参数,并编写一个在任务时调用的小函数结束(并且它将作为参数接收您的worker()函数返回的任何内容。)

守护进程中的魔鬼

事实证明,您真实应用中的工作流程(无论出于何种原因)都希望创建自己的流程,而Pool创建的流程无法做到这一点。他们被创建为&#34;守护进程&#34;流程。来自文档:

  

当进程退出时,它会尝试终止所有守护进程子进程。

     

请注意,不允许守护进程创建子进程。否则,守护进程会在子进程退出时终止其子进程。

非常清楚,因为泥;-)这是一个精心设计的方式来推动自己的Pool工作,创建非守护进程,但对我的口味过于精细:

Python Process Pool non-daemonic?

回到原来的设计,你已经知道它可以工作,我只是改变它来分离定期加入工作进程的逻辑和操作队列的逻辑。从逻辑上讲,它们实际上彼此无关。具体来说,创建一个&#34;后台线程&#34;加入对我来说很有道理:

def reap(workers, quit):
    from time import sleep
    while not quit.is_set():
        to_join = [w for w in workers if not w.is_alive()]
        for p_worker in to_join:
            print(f"Join {p_worker.name}")
            p_worker.join()
            workers.remove(p_worker)
        sleep(2)  # whatever you like
    for p_worker in workers:
        print(f"Join {p_worker.name}")
        p_worker.join()

def receive(q: mp.Queue):
    import threading
    workers = []  # type: List[mp.Process]
    quit = threading.Event()
    reaper = threading.Thread(target=reap, args=(workers, quit))
    reaper.start()

    while True:
        request = q.get()
        if request == "EOF":
            break
        p_worker = mp.Process(target=worker, args=(request,), name=request)
        p_worker.start()
        workers.append(p_worker)

    quit.set()
    reaper.join()

我碰巧知道list.append()list.remove()在CPython中是线程安全的,所以没有需要用锁来保护这些操作。但如果添加一个就不会受到伤害。

还有一个尝试

虽然Pool创建的进程是守护进程,但类似concurrent.futures.ProcessPoolExecutor创建的进程似乎不是。所以我的第一个建议的这个简单变​​化可能适合你(或可能不适用;-)):

NUM_WORKERS = 4

def receive(q: mp.Queue):
    import concurrent.futures as cf
    with cf.ProcessPoolExecutor(NUM_WORKERS) as e:
        while True:
            request = q.get()
            if request == "EOF":
                break
            e.submit(worker, request)

如果这对您有用,那么很难想象任何更简单的东西。

答案 1 :(得分:2)

嗯,一个解决方案是使用像python rq或selery这样的工作队列。基本上你会产生n个工人(单独的进程),它们会查看要执行的任务的队列,然后在你的主程序中你只需要推送队列中的任务并定期检查结果。