我有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
答案 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小部件?从您发布的代码中,我会尝试这样做:
print
中添加updatelog
语句。如果由于某种原因(例如after()
调用失败而未持续调用此项,则queuelog
将继续无限制地增长。updatelog
,请将焦点转为printlog
。注释此函数的内容以查看是否仍然出现内存不足错误。如果他们不这样做,那么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()
答案 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()
我注意到你没有在发送循环中的任何地方睡觉(),这意味着数据尽可能快地被读取并且尽可能快地发送。请注意,播放媒体文件时这不是理想的行为 - 容器时间戳可以指示数据速率。