我正在用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>
答案 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个工人(单独的进程),它们会查看要执行的任务的队列,然后在你的主程序中你只需要推送队列中的任务并定期检查结果。