Queue.get() 内存泄漏

时间:2021-04-22 09:05:02

标签: python memory-leaks queue

在使用 get() 方法清空队列后,我使用以下代码生成看起来像内存泄漏的内容。

import queue
import os
import psutil

def run(del_after_puts, del_after_gets, n_puts, process):

        mem = queue.Queue()
    
        for msg in range(n_puts):
            msg_put = f'{msg}_0000000000000000000000000000000000000000000000000000000000000333333333333331111111111'
            if msg % 1000000 == 0:
                print(f'puting  {msg} qsize {len(mem.queue)}')
            mem.put(msg_put)
    
    
        print(f'------ put done  ----- qsize {len(mem.queue)}')
        print(f'mem_pct {round(process.memory_percent(), 2)}% ')
    
        if del_after_puts:
            print(f'deleting queue after puts {mem}')
            del mem
            print(f'mem_pct {round(process.memory_percent(), 2)}% ')
            return
    
        for _ in range(n_puts):
            msg_get = mem.get()
            msg = int(msg_get.split('_')[0])
            if msg % 1000000 == 0:
                print(f'getting_q {msg} qsize {len(mem.queue)} ')
            mem.task_done()
            
        print(f'------ gets done  ----- qsize {len(mem.queue)}')
        print(f'mem_pct {round(process.memory_percent(), 2)}% ')
    
        if del_after_gets:
            print(f'deleting queue after gets {mem}')
            del mem
            print(f'mem_pct {round(process.memory_percent(), 2)}% ')
            return
    
    if __name__ == '__main__':
        del_after_puts = False
        del_after_gets = False
        n_puts = 20_000_000
        print()
        print('#########')
        print(f'del_after_puts {del_after_puts} del_after_gets {del_after_gets} n_puts {n_puts}')
    
        process = psutil.Process(os.getpid())
        print('before run')
        print(f'mem_pct {round(process.memory_percent(), 2)}% ')
    
        run(del_after_puts, del_after_gets, n_puts, process)
    
        print(f'after run return')
        print(f'mem_pct {round(process.memory_percent(), 2)}% ')

此脚本可以通过 3 种方式运行:

  1. 将 n_puts 元素添加到队列中,然后将其清空。
  2. 将n_puts元素加入队列,然后删除队列对象
  3. 将 n_puts 元素添加到队列中,然后将其清空,然后删除队列对象。

对于第 1 种和第 3 种情况,脚本似乎会产生内存泄漏,如下所示:

第一种情况,在将元素放入队列之前使用的 mem 为 0.15%,清空后为 2.22%:

<块引用>

#########

del_after_puts False del_after_gets False n_puts 20000000

运行前

mem_pct 0.15%

------ 完成 ----- qsize 20000000

mem_pct 37.61%

------ 完成 ----- qsize 0

mem_pct 2.22%

第三种情况,将元素放入队列之前使用的mem为0.15%,清空后为2.22%,删除对象后为2.22%:

<块引用>

#########

del_after_puts False del_after_gets True n_puts 20000000

运行前

mem_pct 0.15%

------ 完成 ----- qsize 20000000

mem_pct 37.61%

------ 完成 ----- qsize 0

mem_pct 2.22%

在0x7fbd87295a10处获取

mem_pct 2.22%

对于第 2 种情况,开始时的 mem_pct 为 0.15%,在将所有元素放入队列并删除它之后,为 0.16%,几乎相同。

<块引用>

#########

del_after_puts True del_after_gets False n_puts 20000000

运行前

mem_pct 0.15%

------ 完成 ----- qsize 20000000

mem_pct 37.61%

后删除队列

mem_pct 0.16%

可以看出,只有在第二种情况下,只有 queue.put() 被调用时,内存才会返回到起始级别,因此 queue.get() 似乎产生了内存泄漏。

这在 python 3.7、3.8 和 3.9 中都是持久的。

我尝试使用 tracemalloc 和 pympler 分析内存,但它们没有在 python 级别显示任何泄漏,因此我怀疑这可能是 C 级别泄漏。

我在应用程序上使用带有线程的队列来进行日志记录,这些应用程序可以运行数周,而队列似乎会导致泄漏并挂起我的应用程序。我能够跟踪那里的泄漏,它们表明这似乎来自队列机制中使用的条件锁中的双端队列,但我从未见过在我的运行应用程序中充满任何元素/等待程序,所以请看图.

threading.py:348:
waiters_to_notify = _deque(_islice(all_waiters, n))

无论如何,有没有办法减轻和处理这种队列泄漏? 谢谢

1 个答案:

答案 0 :(得分:0)

如果有人在寻找解决方案,泄漏的原因似乎是 Queue 实现中使用的股票 python deque。我已经从这里找到的股票中更改了双端队列实现:

https://github.com/kata198/python-cllist

然后重新定义Queue类如下:

from cllist import dllist

class DllistQueue(queue.Queue):
    def _init(self, maxsize):
        self.queue = dllist()

那么用新类测试的结果是:

<块引用>

#########

del_after_puts False del_after_gets True n_puts 20000000

运行前

mem_pct 0.15%

将 0 qsize 0 置为 0

------ 完成 ----- qsize 20000000

mem_pct 47.35%

------ 完成 ----- qsize 0

mem_pct 0.16%

在获取ma​​in.DllistQueue 对象后删除队列 0x7f3a197d8f90>

mem_pct 0.16%

所以这个链表实现似乎没有泄漏,而且它和股票一样快。唯一的缺点是它在顶部使用时使用了更多的内存。

相关问题