我有一个应该永远运行的程序。 这就是我在做的事情:
from myfuncs import do, process
class Worker(multiprocessing.Process):
def __init__(self, lock):
multiprocesing.Process.__init__(self)
self.lock = lock
self.queue = Redis(..) # this is a redis based queue
self.res_queue = Redis(...)
def run():
while True:
job = self.queue.get(block=True)
job.results = process(job)
with self.lock:
post_process(self.res_queue, job)
def main():
lock = multiprocessing.Semaphore(1)
ps = [Worker(lock) for _ in xrange(4)]
[p.start() for p in ps]
[p.join() for p in ps]
self.queue和self.res_queue是两个与python stdlib Queue类似的对象但是它们 使用Redis数据库作为后端。
函数进程对作业所承载的数据(主要是html解析)进行一些处理并返回 字典。
函数post_process通过检查某些条件将作业写入另一个redis队列(一次只有一个进程可以检查锁定原因的标准)。它返回True / False。
该程序每天使用的内存正在增加。 有人能弄明白发生了什么吗?
当运行方法中的作业超出范围时,内存应该是免费的吗?
答案 0 :(得分:5)
当运行方法中的作业超出范围时,内存应该是免费的吗?
首先,范围是整个run
方法,它永远循环,因此永远不会发生。 (此外,当您退出run
方法时,进程将关闭并且其内存无论如何都会被释放...)
但即使它确实超出了范围,这也不意味着你认为它意味着什么。 Python不像C ++,其中存在存储在堆栈中的变量。所有对象都存在于堆中,并且它们保持活动状态,直到不再有对它们的引用为止。超出范围的变量意味着变量不再引用它曾经引用的任何对象。如果该变量是对象的唯一引用,则它将被释放*,但如果您在其他地方有其他引用,则在其他引用消失之前,不能释放该对象。
与此同时,超出范围并没有什么神奇之处。变量停止引用对象的任何方式都具有相同的效果 - 无论是变量超出范围,是否在其上调用del
,或者为其分配新值。因此,每次循环时,当您执行job =
时,即使没有任何内容超出范围,您也会删除先前对job
的引用。 (但请记住,你会在高峰期有两个作业,而不是一个,因为在旧版本被释放之前,新作业将从队列中拉出。如果这是一个问题,你可以随时做在阻塞队列之前job = None
。)
因此,假设问题实际上是job
对象(或它拥有的东西),问题是你没有向我们展示的一些代码是在某处保留对它的引用。
在不知道你在做什么的情况下,很难建议修复。它可能只是“不存储那里”。或者它可能是“存储weakref而不是对象本身”。或者“添加LRU算法”。或者“添加一些流量控制,这样如果你得到太多备份,你就不会继续工作,直到内存耗尽”。
*在CPython中,这会立即发生,因为垃圾收集器基于引用计数。另一方面,在Jython和IronPython中,垃圾收集器只依赖于底层VM的垃圾收集器,因此在JVM或CLR注意到它不再被引用之前,该对象不会被释放,这通常不是立即的,并且是不确定的子>
答案 1 :(得分:4)
如果找不到泄漏源,可以通过让每个工人只处理有限数量的任务来解决这个问题。一旦他们达到任务限制,您就可以让他们退出,并用新的工作进程替换它们。内置的multiprocessing.Pool
对象通过maxtasksperchild
关键字参数支持此功能。你可以做类似的事情:
import multiprocessing
import threading
class WorkerPool(object):
def __init__(self, workers=multiprocessing.cpu_count(),
maxtasksperchild=None, lock=multiprocessing.Semaphore(1)):
self._lock = multiprocessing.Semaphore(1)
self._max_tasks = maxtasksperchild
self._workers = workers
self._pool = []
self._repopulate_pool()
self._pool_monitor = threading.Thread(self._monitor_pool)
self._pool_monitor.daemon = True
self._pool_monitor.start()
def _monitor_pool(self):
""" This runs in its own thread and monitors the pool. """
while True:
self._maintain_pool()
time.sleep(0.1)
def _maintain_pool(self):
""" If any workers have exited, start a new one in its place. """
if self._join_exited_workers():
self._repopulate_pool()
def _join_exited_workers(self):
""" Find exited workers and join them. """
cleaned = False
for i in reversed(range(len(self._pool))):
worker = self._pool[i]
if worker.exitcode is not None:
# worker exited
worker.join()
cleaned = True
del self._pool[i]
return cleaned
def _repopulate_pool(self):
""" Start new workers if any have exited. """
for i in range(self._workers - len(self._pool)):
w = Worker(self._lock, self._max_tasks)
self._pool.append(w)
w.start()
class Worker(multiprocessing.Process):
def __init__(self, lock, max_tasks):
multiprocesing.Process.__init__(self)
self.lock = lock
self.queue = Redis(..) # this is a redis based queue
self.res_queue = Redis(...)
self.max_tasks = max_tasks
def run():
runs = 0
while self.max_tasks and runs < self.max_tasks:
job = self.queue.get(block=True)
job.results = process(job)
with self.lock:
post_process(self.res_queue, job)
if self.max_tasks:
runs += 1
def main():
pool = WorkerPool(workers=4, maxtasksperchild=1000)
# The program will block here since none of the workers are daemons.
# It's not clear how/when you want to shut things down, but the Pool
# can be enhanced to support that pretty easily.
请注意,上面的池监控代码几乎与multiprocessing.Pool
中用于相同目的的代码完全相同。