在队列内部调用时,Python队列内存泄漏

时间:2013-10-03 09:15:10

标签: python multithreading python-2.7 memory-leaks queue

我有python TCP客户端,需要将循环中的媒体(.mpg)文件发送到'C'TCP服务器。

我有以下代码,在单独的线程中,我正在读取10K块文件并发送它并在循环中重复执行,我认为这是因为我的线程模块的实现,或者tcp发送。我正在使用队列在我的GUI(Tkinter)上打印日志,但有些时候它会内存不足。

更新1 - 根据要求添加了更多代码

线程类“Sendmpgthread”用于创建发送数据的线程

.
. 
def __init__ ( self, otherparams,MainGUI):
    .
    .
    self.MainGUI = MainGUI
    self.lock = threading.Lock()
    Thread.__init__(self)

#This is the one causing leak, this is called inside loop
def pushlog(self,msg):
    self.MainGUI.queuelog.put(msg)

def send(self, mysocket, block):
    size = len(block)
    pos = 0;
    while size > 0:
        try:
            curpos = mysocket.send(block[pos:])
        except socket.timeout, msg:
            if self.over:
                 self.pushlog(Exit Send)
                return False
        except socket.error, msg:
            print 'Exception'     
            return False  
        pos = pos + curpos
        size = size - curpos
    return True

def run(self):
    media_file = None
    mysocket = None 

    try:
        mysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        mysocket.connect((self.ip, string.atoi(self.port)))
        media_file = open(self.file, 'rb') 

        while not self.over:
            chunk = media_file.read(10000)
            if not chunk:   # EOF Reset it
                print 'resetting stream'
                media_file.seek(0, 0)
                continue
            if not self.send(mysocket, chunk): # If some error or thread is killed 
                break;

            #disabling this solves the issue
            self.pushlog('print how much data sent')       

    except socket.error, msg:
        print 'print exception'
    except Exception, msg:
        print 'print exception'

    try:
        if media_file is not None:
            media_file.close()
            media_file = None            
        if mysocket is not None:
            mysocket.close()
            mysocket = None
    finally:
            print 'some cleaning'   

def kill(self):
    self.over = True

我发现这是因为Queue的错误实现是评论该部分解决了问题

UPDATE 2 - 从上面的Thread类调用的MainGUI类

class MainGUI(Frame):
    def __init__(self, other args):
       #some code
       .
       .
        #from the above thread class used to send data
        self.send_mpg_status = Sendmpgthread(params)
        self.send_mpg_status.start()     
        self.after(100, self.updatelog)
        self.queuelog = Queue.Queue()

    def updatelog(self):
       try:
           msg = self.queuelog.get_nowait() 

           while msg is not None:
               self.printlog(msg)
               msg = self.queuelog.get_nowait() 
        except Queue.Empty:
           pass

        if self.send_mpg_status: # only continue when sending   
            self.after(100, self.updatelog)

    def printlog(self,msg):
        #print in GUI

4 个答案:

答案 0 :(得分:4)

我看不到您的代码段明显错误。

为了减少Python 2.7下的内存使用量,我使用buffer(block, pos)代替block[pos:]。我也使用mysocket.sendall(block)代替您的send方法。

如果上述想法无法解决您的问题,那么该错误很可能是代码中的其他地方。您能否发布完整的Python脚本的最短版本,该脚本仍然存在内存不足(http://sscce.org/)?这会增加你获得有用帮助的变化。

答案 1 :(得分:3)

由于printlog正在添加到tkinter文本控件,因此该控件占用的内存将随每条消息一起增长(它必须存储所有日志消息才能显示它们)。

除非存储所有日志至关重要,否则常见的解决方案是限制显示的最大日志行数。

一个天真的实现是在控件达到最大消息数后从开头消除额外的行。将函数添加到get the number of lines in the control,然后在printlog中添加类似于:

的函数
while getnumlines(self.edit) > self.maxloglines:
    self.edit.delete('1.0', '1.end')

(以上代码未经测试)

更新:一些通用指南

请记住,看起来像内存泄漏并不总是意味着函数是wrong,或者内存不再可访问。很多时候,缺少正在积累元素的容器的清理代码。

解决此类问题的基本方法:

  • 对代码的哪个部分可能导致问题形成意见
  • 通过评论该代码来检查(或者在找到候选人之前继续评论代码)
  • 在负责的代码中查找容器,添加代码以打印其大小
  • 决定可以从该容器中安全删除哪些元素,以及何时执行此操作
  • 测试结果

答案 2 :(得分:2)

内存不足错误表示正在生成但未被消耗或释放的数据。仔细查看您的代码,我猜这两个方面:

  • 正在将消息推送到Queue.Queue()方法中的pushlog实例。它们被消耗了吗?
  • MainGui printlog方法可能是在某处写文字。例如。它是否在不修改消息的情况下不断写入某种GUI小部件?

从您发布的代码中,我会尝试这样做:

  1. print中添加updatelog语句。如果由于某种原因(例如after()调用失败而未持续调用此项,则queuelog将继续无限制地增长。
  2. 如果不断调用updatelog,请将焦点转为printlog。注释此函数的内容以查看是否仍然出现内存不足错误。如果他们不这样做,那么printlog中的某些内容可能会保留已记录的数据,您需要深入挖掘以找出内容。
  3. 除此之外,代码可以稍微清理一下。直到线程启动之后才会创建self.queuelog,这会导致竞争条件,其中线程可能会在创建之前尝试写入队列。在线程启动之前,应将queuelog的创建移动到某处。

    updatelog也可以重构以删除冗余:

    def updatelog(self):
           try:
               while True:
                   msg = self.queuelog.get_nowait() 
                   self.printlog(msg)
            except Queue.Empty:
               pass
    

    我假设从GUI线程调用kill函数。为了避免线程竞争条件,self.over应该是线程安全变量,例如threading.Event对象。

    def __init__(...):
        self.over = threading.Event()
    
    def kill(self):
        self.over.set()
    

答案 3 :(得分:1)

TCP发送循环中没有数据堆积。

内存错误可能是由日志队列引起的,因为您还没有发布完整代码,请尝试使用以下类进行日志记录:

from threading import Thread, Event, Lock
from time import sleep, time as now


class LogRecord(object):
    __slots__ = ["txt", "params"]
    def __init__(self, txt, params):
        self.txt, self.params = txt, params

class AsyncLog(Thread):
    DEBUGGING_EMULATE_SLOW_IO = True

    def __init__(self, queue_max_size=15, queue_min_size=5):
        Thread.__init__(self)
        self.queue_max_size, self.queue_min_size = queue_max_size, queue_min_size
        self._queuelock = Lock()
        self._queue = []            # protected by _queuelock
        self._discarded_count = 0   # protected by _queuelock
        self._pushed_event = Event()
        self.setDaemon(True)
        self.start()

    def log(self, message, **params):
        with self._queuelock:
            self._queue.append(LogRecord(message, params))
            if len(self._queue) > self.queue_max_size:
                # empty the queue:
                self._discarded_count += len(self._queue) - self.queue_min_size
                del self._queue[self.queue_min_size:] # empty the queue instead of creating new list (= [])
            self._pushed_event.set()

    def run(self):
        while 1: # no reason for exit condition here
            logs, discarded_count = None, 0
            with self._queuelock:
                if len(self._queue) > 0:
                    # select buffered messages for printing, releasing lock ASAP
                    logs = self._queue[:]
                    del self._queue[:]
                    self._pushed_event.clear()
                    discarded_count = self._discarded_count
                    self._discarded_count = 0
            if not logs:
                self._pushed_event.wait()
                self._pushed_event.clear()
                continue
            else:
                # print logs
                if discarded_count:
                    print ".. {0} log records missing ..".format(discarded_count)
                for log_record in logs:
                    self.write_line(log_record)
                if self.DEBUGGING_EMULATE_SLOW_IO:
                    sleep(0.5)

    def write_line(self, log_record):
        print log_record.txt, " ".join(["{0}={1}".format(name, value) for name, value in log_record.params.items()])



if __name__ == "__main__":
    class MainGUI:
        def __init__(self):
            self._async_log = AsyncLog()
            self.log = self._async_log.log # stored as bound method

        def do_this_test(self):
            print "I am about to log 100 times per sec, while text output frequency is 2Hz (twice per second)"

            def log_100_records_in_one_second(itteration_index):
                for i in xrange(100):
                    self.log("something happened", timestamp=now(), session=3.1415, itteration=itteration_index)
                    sleep(0.01)

            for iter_index in range(3):
                log_100_records_in_one_second(iter_index)

    test = MainGUI()
    test.do_this_test()

我注意到你没有在发送循环中的任何地方睡觉(),这意味着数据尽可能快地被读取并且尽可能快地发送。请注意,播放媒体文件时这不是理想的行为 - 容器时间戳可以指示数据速率。