Python在线程内调用时将内存泄漏排入队列
我有python TCP客户端,需要将循环中的媒体(.mpg)文件发送到“C”TCP服务器 我有下面的代码,在单独的线程中,我读取文件的10K块并发送它,然后在循环中重新执行,我认为这是因为我实现了线程模块,或tcp发送。我正在使用队列在GUI(Tkinter)上打印日志,但过了一段时间后,它的内存就用完了。 更新1-根据要求添加更多代码 线程类“Sendmpgthread”用于创建发送数据的线程Python在线程内调用时将内存泄漏排入队列,python,multithreading,python-2.7,memory-leaks,queue,Python,Multithreading,Python 2.7,Memory Leaks,Queue,我有python TCP客户端,需要将循环中的媒体(.mpg)文件发送到“C”TCP服务器 我有下面的代码,在单独的线程中,我读取文件的10K块并发送它,然后在循环中重新执行,我认为这是因为我实现了线程模块,或tcp发送。我正在使用队列在GUI(Tkinter)上打印日志,但过了一段时间后,它的内存就用完了。 更新1-根据要求添加更多代码 线程类“Sendmpgthread”用于创建发送数据的线程 . . def __init__ ( self, otherparams,MainGUI):
.
.
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
我发现这是因为对队列的错误实现,因为对这篇文章的注释解决了这个问题
更新2-从上面的线程类调用的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
我看不出你的代码片段有任何明显的错误 为了在Python2.7下稍微减少内存使用,我将使用
buffer(block,pos)
而不是block[pos://code>。另外,我会使用mysocket.sendall(block)
而不是send
方法
如果上面的想法不能解决您的问题,那么这个bug很可能存在于代码的其他地方。您能发布完整Python脚本的最短版本吗?该脚本的内存仍然不足()?这会增加您获得有用帮助的变化。内存不足错误表示数据正在生成,但未被消耗或释放。通过查看您的代码,我会猜测以下两个方面:
- 正在将消息推送到
pushlog
方法中的Queue.Queue()
实例上。它们正在被消费吗
MainGui
printlog
方法可能正在某处写入文本。它是否在不删减任何消息的情况下不断地写入某种GUI小部件
根据您发布的代码,我将尝试以下内容:
在updatelog
中放入print
语句。如果由于某些原因(例如失败的after()
调用)而没有持续调用此函数,则queuelog
将继续增长而不受限制
如果不断调用updatelog
,则将焦点转到printlog
。注释此函数的内容,查看是否仍出现内存不足错误。如果没有,则打印日志中的某些内容可能会保留记录的数据,您需要深入挖掘以找出原因
除此之外,代码还可以稍微清理一下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()
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()
我注意到发送循环中的任何地方都没有sleep(),这意味着数据的读取速度和发送速度都尽可能快。请注意,播放媒体文件时,这不是理想的行为-容器时间戳用于指示数据速率。由于printlog添加到tkinter文本控件中,该控件占用的内存将随着每条消息的增加而增加(它必须存储所有日志消息才能显示它们)
除非存储所有日志非常重要,否则通常的解决方案是限制显示的最大日志行数
一个简单的实现是在控件达到最大消息数后从一开始就消除多余的行。在printlog中添加一个函数,然后类似于:
while getnumlines(self.edit) > self.maxloglines:
self.edit.delete('1.0', '1.end')
(以上代码未测试)
更新:一些通用指南
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
请记住,看起来像内存泄漏的情况并不总是意味着函数错误,或者内存不再可访问。很多时候,对于正在积累元素的容器,缺少清理代码
这类问题的基本通用方法:
- 就代码的哪一部分可能导致问题形成意见
- 通过注释该代码检查它(或者在找到候选代码之前一直注释代码)
- 在责任代码中查找容器,添加代码以打印其大小
- 决定哪些元素可以安全地从容器中移除,以及何时移除
- 测试结果
为什么“需要在循环中运行它”?需求不应该规定这样的细节。除非您处于非阻塞模式,否则一次发送将花费全部时间。服务器拾取文件并播放,并且要求在循环中播放,我无法将文件发送到服务器并让它从那里播放,因为我们已经反向设计了完整的linux内核以及从客户端控制的媒体播放器。您可以通过调用self.printlog(msg)进行注释吗,要查看问题是在实际队列中还是在打印日志中?如果您不断调用self.edit.insert,则编辑控件使用的内存将始终增加。但是没有必要去猜测,只要把它评论出来,看看会发生什么。内存泄漏通常并不意味着函数“不工作”,但某个地方正在“积累内存”,控制台将只显示最后的X日志消息,您可以对编辑控件执行相同的操作。我会补充一个答案,因为评论不是最好的地方。您还应该更新您的问题,提及tkinter self.edit.in