正如@Giannis在一篇评论中建议的那样,您正在从头开始重塑流程管理器。坚持Python的特性,您是否反对使用multiprocessing.Pool?如果是,怎么办?在
通常的方法是选择要同时运行的最大工作进程数。说NUM_WORKERS = 4
然后将它作为receive()函数的替代:
^{pr2}$
NUM_WORKERS进程只创建一次,并跨任务重用。如果出于某种原因,您需要(或希望)为每个任务创建一个全新的进程,则只需将maxtasksperchild=1添加到Pool构造函数中。在
如果出于某种原因,您需要知道每个任务何时完成,您可以,例如,在apply_async()调用中添加一个callback=参数,并编写一个小函数,该函数将在任务结束时被调用(它将作为参数接收worker()函数返回的内容)。在
恶魔就在恶魔之列
因此,您的实际应用程序中的工作进程希望(无论出于什么原因)创建自己的进程,Pool创建的进程不能这样做。它们被创建为“守护进程”。从文件中:When a process exits, it attempts to terminate all of its daemonic child processes.
Note that a daemonic process is not allowed to create child processes. Otherwise a daemonic process would leave its children orphaned if it gets terminated when its parent process exits.
非常清楚;-)这里有一种精心设计的方法来创建您自己的Pool工作方式,它创建了非守护进程,但对我的口味来说太过复杂了:
回到您原来的设计,您已经知道它是可行的,我只需更改它,将周期性地加入工作进程的逻辑与操作队列的逻辑分开。从逻辑上讲,他们真的没有任何关系。具体来说,创建一个“后台线程”来加入对我来说很有意义: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()和{}在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)
如果这对你有用,很难想象有什么实质上更简单的事情。在