Python 如何在不阻塞数据流的情况下保存数据流中的数据?(PyQt5信号发射()性能)

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

我正在开发一个PyQt5应用程序。 在我的应用程序中,它有一个数据流,其速度约为5~20数据/秒

每次数据到达时,调用类
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秒

我的问题是三件事

  • signal emit()函数的性能不是这样好吗

  • 在我的例子中,是否有emit()函数的替代方法

  • 或者有没有办法在不阻塞数据流的情况下保存pickle? (如有任何修改我的代码的建议,我们将不胜感激)


  • 谢谢你阅读这个长长的问题

    像这样的东西可能有用

    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