python multiprocessing.Pool kill *特定*长时间运行或挂起的进程
Posted
技术标签:
【中文标题】python multiprocessing.Pool kill *特定*长时间运行或挂起的进程【英文标题】:python multiprocessing.Pool kill *specific* long running or hung process 【发布时间】:2013-12-02 01:24:08 【问题描述】:我需要执行一个包含许多并行数据库连接和查询的池。我想使用 multiprocessing.Pool 或 concurrent.futures ProcessPoolExecutor。 Python 2.7.5
在某些情况下,查询请求耗时过长或永远无法完成(挂起/僵尸进程)。我想从已超时的 multiprocessing.Pool 或 concurrent.futures ProcessPoolExecutor 中终止 特定 进程。
这是一个如何杀死/重新生成整个进程池的示例,但理想情况下,我会尽量减少 CPU 抖动,因为我只想杀死超时秒后未返回数据的特定长时间运行的进程。
由于某种原因,在所有结果返回并完成后,下面的代码似乎无法终止/加入进程池。它可能与在超时发生时杀死工作进程有关,但是当它们被杀死并且结果符合预期时,池会创建新的工作进程。
from multiprocessing import Pool
import time
import numpy as np
from threading import Timer
import thread, time, sys
def f(x):
time.sleep(x)
return x
if __name__ == '__main__':
pool = Pool(processes=4, maxtasksperchild=4)
results = [(x, pool.apply_async(f, (x,))) for x in np.random.randint(10, size=10).tolist()]
while results:
try:
x, result = results.pop(0)
start = time.time()
print result.get(timeout=5), '%d done in %f Seconds!' % (x, time.time()-start)
except Exception as e:
print str(e)
print '%d Timeout Exception! in %f' % (x, time.time()-start)
for p in pool._pool:
if p.exitcode is None:
p.terminate()
pool.terminate()
pool.join()
【问题讨论】:
【参考方案1】:我没有完全理解你的问题。你说你想停止一个特定的进程,但是在你的异常处理阶段,你在所有作业上调用终止。不知道你为什么这样做。另外,我很确定使用来自multiprocessing.Pool
的内部变量不是很安全。说了这么多,我认为你的问题是为什么这个程序在超时发生时没有完成。如果这是问题所在,那么以下方法可以解决问题:
from multiprocessing import Pool
import time
import numpy as np
from threading import Timer
import thread, time, sys
def f(x):
time.sleep(x)
return x
if __name__ == '__main__':
pool = Pool(processes=4, maxtasksperchild=4)
results = [(x, pool.apply_async(f, (x,))) for x in np.random.randint(10, size=10).tolist()]
result = None
start = time.time()
while results:
try:
x, result = results.pop(0)
print result.get(timeout=5), '%d done in %f Seconds!' % (x, time.time()-start)
except Exception as e:
print str(e)
print '%d Timeout Exception! in %f' % (x, time.time()-start)
for i in reversed(range(len(pool._pool))):
p = pool._pool[i]
if p.exitcode is None:
p.terminate()
del pool._pool[i]
pool.terminate()
pool.join()
关键是您需要从池中移除项目;仅仅对它们调用终止是不够的。
【讨论】:
【参考方案2】:在您的解决方案中,您正在篡改池本身的内部变量。池依赖 3 个不同的线程才能正确运行,在不真正知道你在做什么的情况下干预它们的内部变量是不安全的。
在标准 Python 池中没有一种干净的方法来停止进程超时,但有一些替代实现可以公开此类功能。
您可以查看以下库:
pebble
billiard
【讨论】:
【参考方案3】:为避免访问内部变量,您可以将执行任务中的multiprocessing.current_process().pid
保存到共享内存中。然后从主进程遍历multiprocessing.active_children()
,如果存在则杀死目标pid
。
然而,在工人的这种外部终止之后,它们被重新创建,但是池变得不可加入并且还需要在join()
【讨论】:
这假定没有其他活动的子代不属于池。【参考方案4】:我也遇到过这个问题。
@stacksia 的原始代码和编辑版本有同样的问题:
在这两种情况下,当仅其中一个进程达到超时时(即,当pool._pool
上的循环完成时),它将杀死所有当前正在运行的进程。
在下面找到我的解决方案。它涉及按照@luart 的建议为每个工作进程创建一个.pid
文件。如果有一种方法可以标记每个工作进程,它将起作用(在下面的代码中,x
完成了这项工作)。
如果有人有更优雅的解决方案(例如将 PID 保存在内存中),请分享。
#!/usr/bin/env python
from multiprocessing import Pool
import time, os
import subprocess
def f(x):
PID = os.getpid()
print 'Started:', x, 'PID=', PID
pidfile = "/tmp/PoolWorker_"+str(x)+".pid"
if os.path.isfile(pidfile):
print "%s already exists, exiting" % pidfile
sys.exit()
file(pidfile, 'w').write(str(PID))
# Do the work here
time.sleep(x*x)
# Delete the PID file
os.remove(pidfile)
return x*x
if __name__ == '__main__':
pool = Pool(processes=3, maxtasksperchild=4)
results = [(x, pool.apply_async(f, (x,))) for x in [1,2,3,4,5,6]]
pool.close()
while results:
print results
try:
x, result = results.pop(0)
start = time.time()
print result.get(timeout=3), '%d done in %f Seconds!' % (x, time.time()-start)
except Exception as e:
print str(e)
print '%d Timeout Exception! in %f' % (x, time.time()-start)
# We know which process gave us an exception: it is "x", so let's kill it!
# First, let's get the PID of that process:
pidfile = '/tmp/PoolWorker_'+str(x)+'.pid'
PID = None
if os.path.isfile(pidfile):
PID = str(open(pidfile).read())
print x, 'pidfile=',pidfile, 'PID=', PID
# Now, let's check if there is indeed such process runing:
for p in pool._pool:
print p, p.pid
if str(p.pid)==PID:
print 'Found it still running!', p, p.pid, p.is_alive(), p.exitcode
# We can also double-check how long it's been running with system 'ps' command:"
tt = str(subprocess.check_output('ps -p "'+str(p.pid)+'" o etimes=', shell=True)).strip()
print 'Run time from OS (may be way off the real time..) = ', tt
# Now, KILL the m*$@r:
p.terminate()
pool._pool.remove(p)
pool._repopulate_pool()
# Let's not forget to remove the pidfile
os.remove(pidfile)
break
pool.terminate()
pool.join()
很多人建议使用鹅卵石。它看起来不错,但仅适用于 Python 3。如果有人有办法为 python 2.6 导入 pebble - 那就太好了。
【讨论】:
Pebble 支持 python 2,但仅在 python 2.7 上测试过。以上是关于python multiprocessing.Pool kill *特定*长时间运行或挂起的进程的主要内容,如果未能解决你的问题,请参考以下文章