Python 如何在不阻塞数据流的情况下保存数据流中的数据?(PyQt5信号发射()性能)
我正在开发一个PyQt5应用程序。 在我的应用程序中,它有一个数据流,其速度约为5~20数据/秒 每次数据到达时,调用类Python 如何在不阻塞数据流的情况下保存数据流中的数据?(PyQt5信号发射()性能),python,pyqt,python-multithreading,pyqt5,qthread,Python,Pyqt,Python Multithreading,Pyqt5,Qthread,我正在开发一个PyQt5应用程序。 在我的应用程序中,它有一个数据流,其速度约为5~20数据/秒 每次数据到达时,调用类Analyzer的以下onData()方法。(以下代码是我的应用程序的简化代码) 但问题是,这个dataDeque对象太大(50~150MB),因此转储pickle大约需要1~2秒 在那一刻(1~2秒),调用onData()方法的请求排队,1~2秒后,排队的请求同时调用大量onData()方法,最终扭曲数据的createdTime 为了解决这个问题,我编辑了我的代码,使用Thr
Analyzer
的以下onData()
方法。(以下代码是我的应用程序的简化代码)
但问题是,这个dataDeque对象太大(50~150MB),因此转储pickle大约需要1~2秒
在那一刻(1~2秒),调用onData()
方法的请求排队,1~2秒后,排队的请求同时调用大量onData()
方法,最终扭曲数据的createdTime
为了解决这个问题,我编辑了我的代码,使用Thread(QThread)来保存pickle
以下代码是已编辑的代码
from PickleDumpingThread import PickleDumpingThread
pickleDumpingThread = PickleDumpingThread()
pickleDumpingThread.start()
class Analyzer():
def __init__(self):
self.cnt = 0
self.dataDeque = deque(MAXLENGTH=10000)
def onData(self, data):
self.dataDeque.append({
"data": data,
"createdTime": time.time()
})
self.cnt += 1
if self.cnt % 10000 == 0:
pickleDumpingThread.pickleDumpingSignal.emit({
"action": savePickle,
"deque": self.dataDeque
})
# pickle.dump(dataDeque, open(file, 'wb'))
下面的代码是PickleDumpingThread
class
class PickleDumpingThread(QThread):
def __init__(self):
super().__init__()
self.daemon = True
self.pickleDumpingSignal[dict].connect(self.savePickle)
def savePickle(self, signal_dict):
pickle.dump(signal_dict["deque"], open(file, 'wb'))
我预计这段新编辑的代码将显著减少流阻塞时间(1~2秒),但这段代码仍然会阻塞流约0.5~2秒
看起来像是pickleDumpingThread.pickleDumpingSignal.emit(somedict)
需要0.5~2秒
我的问题是三件事
谢谢你阅读这个长长的问题 像这样的东西可能有用
class PickleDumpingThread(QThread):
def __init__(self, data):
super().__init__()
self.data = data
def run(self):
pickle.dump(self.data["deque"], open(file, 'wb'))
self.emit(QtCore.SIGNAL('threadFinished(int)'), self.currentThreadId())
class Analyzer():
def __init__(self):
self.cnt = 0
self.dataDeque = deque(MAXLENGTH=10000)
self.threadHandler = {}
def onData(self, data):
self.dataDeque.append({ "data": data, "createdTime": time.time() })
self.cnt += 1
if self.cnt % 10000 == 0:
thread = PickleDumpingThread(self.dataDeque)
self.connect(thread, QtCore.SIGNAL("threadFinished(int)"), self.threadFinished)
thread.start()
self.threadHandler[thread.currentThreadId()] = thread
@QtCore.pyqtSlot(int)
def threadFinished(id):
del self.threadHandler[id]
self.threadHandler
只需知道有多少线程仍在运行,您可以将其清除,然后使用threadFinished
method类似的方法即可
class PickleDumpingThread(QThread):
def __init__(self, data):
super().__init__()
self.data = data
def run(self):
pickle.dump(self.data["deque"], open(file, 'wb'))
self.emit(QtCore.SIGNAL('threadFinished(int)'), self.currentThreadId())
class Analyzer():
def __init__(self):
self.cnt = 0
self.dataDeque = deque(MAXLENGTH=10000)
self.threadHandler = {}
def onData(self, data):
self.dataDeque.append({ "data": data, "createdTime": time.time() })
self.cnt += 1
if self.cnt % 10000 == 0:
thread = PickleDumpingThread(self.dataDeque)
self.connect(thread, QtCore.SIGNAL("threadFinished(int)"), self.threadFinished)
thread.start()
self.threadHandler[thread.currentThreadId()] = thread
@QtCore.pyqtSlot(int)
def threadFinished(id):
del self.threadHandler[id]
self.threadHandler
就是要知道还有多少线程还在运行,你可以去掉它和threadFinished
方法问题是我没有正确使用QThread
印刷的结果
print("(Current Thread)", QThread.currentThread(),"\n")
print("(Current Thread)", int(QThread.currentThreadId()),"\n")
注意到我创建的PickleDumpingThread
在主线程中运行,而不是在某个单独的线程中运行
原因是run()
是QThread
中唯一在单独线程中运行的函数,因此QThread
中的savePickle
方法在主线程中运行
第一个解决方案 使用信号的正确用法是使用工人,如下所示
from PyQt5.QtCore import QThread
class GenericThread(QThread):
def run(self, *args):
# print("Current Thread: (GenericThread)", QThread.currentThread(),"\n")
self.exec_()
class PickleDumpingWorker(QObject):
pickleDumpingSignal = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.pickleDumpingSignal[dict].connect(self.savePickle)
def savePickle(self, signal_dict)
pickle.dump(signal_dict["deque"], open(file, "wb"))
pickleDumpingThread = GenericThread()
pickleDumpingThread.start()
pickleDumpingWorker = PickleDumpingWorker()
pickleDumpingWorker.moveToThread(pickleDumpingThread)
class Analyzer():
def __init__(self):
self.cnt = 0
self.dataDeque = deque(MAXLENGTH=10000)
def onData(self, data):
self.dataDeque.append({
"data": data,
"createdTime": time.time()
})
self.cnt += 1
if self.cnt % 10000 == 0:
pickleDumpingWorker.pickleDumpingSignal.emit({
"action": savePickle,
"deque": self.dataDeque
})
# pickle.dump(dataDeque, open(file, 'wb'))
此解决方案有效(pickle被转储到单独的线程中),但其缺点是由于signal emit()函数,数据流仍然延迟约0.5~1秒
我发现对于我的案例来说,最好的解决方案是@PYPL的代码,但是代码需要一些修改才能工作
最终解决方案 最终的解决方案是修改@PYPL的以下代码
thread = PickleDumpingThread(self.dataDeque)
thread.start()
到
原始代码有一些运行时错误。线程似乎在转储pickle之前被垃圾收集,因为在onData()
函数完成后没有对该线程的引用
通过添加self引用线程。线程解决了此问题
另外,在新的PickleDumpingThread
被self.thread
引用之后,旧的PickleDumpingThread
似乎正在被垃圾收集(因为旧的PickleDumpingThread
丢失其引用)
但是,这个声明没有得到验证(因为我不知道如何查看当前活动线程)
不管怎样,这个解决方案解决了问题
编辑
我的最终解决方案也推迟了。调用Thread.start()需要一些时间
我选择的最终解决方案是在线程中运行无限循环,并监视该线程的一些变量,以确定何时保存pickle。仅仅在线程中使用无限循环需要大量的cpu,所以我添加了time.sleep(0.1)来减少cpu的使用
最终编辑
好的。我的“真正的最终解决方案”也有延迟。。
即使我将转储作业移动到另一个QThread,主线程仍然会延迟pickle转储时间!真奇怪
但我找到了原因。原因既不是emit()性能,也不是我所想的
令人尴尬的是,原因是
所以在这种情况下,我可能应该使用模块
我将在修改代码以使用模块后发布结果
使用多处理后编辑
模块和以后的尝试
使用多处理
模块
使用多处理
模块解决了并发运行python代码的问题,但出现了新的基本问题。新的问题是“在进程之间传递共享内存变量需要相当长的时间”(在我的例子中,将deque
object传递给子进程需要1~2秒)。我发现,只要我使用多处理
模块,这个问题就无法解决。所以我放弃了使用多处理模块
未来可能的尝试
1。仅在QThread中执行文件I/O
pickle转储的基本问题不是写入文件,而是在写入文件之前序列化。Python在写入文件时会释放GIL,因此磁盘I/O可以在QThread
中并发完成。问题是,在pickle.dump
方法中写入文件之前,将deque
对象序列化为字符串需要一些时间,在此期间,由于GIL,主线程将被阻塞
因此,以下方法将有效地减少延迟的长度
每次调用onData()
时,我们都会以某种方式字符串化数据对象,并将其推送到deque对象
在pickle转储中
self.thread = PickleDumpingThread(self.dataDeque)
self.thread.start()
import pickle
class SubList:
on_pickling = None
def __init__(self, sublist):
print('SubList', sublist)
self.data = sublist
def __getstate__(self):
if SubList.on_pickling is not None:
print('SubList pickle state fetch: calling sub callback')
SubList.on_pickling()
return self.data
def __setstate__(self, obj):
if SubList.on_pickling is not None:
print('SubList pickle state restore: calling sub callback')
SubList.on_pickling()
self.data = obj
class ListSubPickler:
def __init__(self, data: list):
self.data = data
def __getstate__(self):
print('creating SubLists for pickling long list')
num_chunks = 10
span = int(len(self.data) / num_chunks)
SubLists = [SubList(self.data[i:(i + span)]) for i in range(0, len(self.data), span)]
return SubLists
def __setstate__(self, subpickles):
self.data = []
print('restoring Pickleable(list)')
for subpickle in subpickles:
self.data.extend(subpickle.data)
print('final', self.data)
def refresh():
# do something: refresh GUI (for example, qApp.processEvents() for Qt), show progress, etc
print('refreshed')
data = list(range(100)) # your large data object
list_pickler = ListSubPickler(data)
SubList.on_pickling = refresh
print('\ndumping pickle of', list_pickler)
pickled = pickle.dumps(list_pickler)
print('\nloading from pickle')
new_list_pickler = pickle.loads(pickled)
assert new_list_pickler.data == data
print('\nloading from pickle, without on_pickling')
SubList.on_pickling = None
new_list_pickler = pickle.loads(pickled)
assert new_list_pickler.data == data