Python多处理内存增加

时间:2014-09-12 07:32:57

标签: python memory multiprocessing

我有一个应该永远运行的程序。 这就是我在做的事情:

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。

该程序每天使用的内存正在增加。 有人能弄明白发生了什么吗?

当运行方法中的作业超出范围时,内存应该是免费的吗?

2 个答案:

答案 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中用于相同目的的代码完全相同。