Python PyQT QThreadPool 线程池运行后整个界面卡死问题

最近在写Excel按照模板生成工具。

使用的环境:python3.7 + PYQT5 + MSsql +xlwings +json

在写完界面和业务逻辑之后,开始尝试写多线程。一开始的思路是在导出时生成一个线程池->线程池分别插入线程并且开始。

这一切都在debug 断点模式 下运行好好的。

代码大致内容如下:

from PyQt5.QtCore import QRunnable,QThreadPool,QThread

def method():
    '''
        线程池添加且运行线程内容
    '''
    threadpool = QThreadPool()
    threadpool.globalInstance()
    threadpool.setMaxThreadCount(5)
    for i in item:    #item为QTable类内容
        mythread = HnThreadForExcel_Argv(i.text(),thfs,mbedate,mafdate,threadsignal)
        threadpool.start(i)
    
class HnThreadForExcel_Argv(QRunnable):
    '''
        为Excel生成提供PYQT线程类
    '''

    def __init__(self,_suplier,dfs,_before_date,_after_date,_Hnsignal=None):
        super().__init__()
        self._suplier =_suplier
        self.fs = dfs
        self._before_date = _before_date
        self._after_date = _after_date
        self.signal = _Hnsignal
        self.setAutoDelete(True)
    def run(self):
        ...
        #各种方法

    def __del__(self):
        pass

在线程池

threadpool.start(i)

之前通过断点停留,并且再次运行时,程序成功运行。

然而,在程序不打断点情况下直接运行,程序卡死····而且由于线程池卡死关系,在线程类中重新debug下无法捕获到卡死代码位置。

首先想到的问题可能是,程序死锁了。但是如何死锁我并不清楚。通过交友网站基佬们的建议,我设置了线程们的优先权。

果然还是不行。

通过浅薄的知识,我设想多线程可能因为线程之间资源竞争导致程序死锁。

于是我为多个线程添加了副本,如下:

from PyQt5.QtCore import QRunnable,QThreadPool,QThread

def method():
    '''
        线程池添加且运行线程内容
    '''
    threadpool = QThreadPool()
    threadpool.globalInstance()
    threadpool.setMaxThreadCount(5)
    for i in item:    #item为QTable类内容
        fs = thfs
        bedate = mbedate
        aferdate = mafdate
        signal = threadsignal
        mythread = HnThreadForExcel_Argv(i.text(),fs,bedate,aferdate,signal)
        threadpool.start(i)
    
class HnThreadForExcel_Argv(QRunnable):
    '''
        为Excel生成提供PYQT线程类
    '''

    def __init__(self,_suplier,dfs,_before_date,_after_date,_Hnsignal=None):
        super().__init__()
        self._suplier =_suplier
        self.fs = dfs
        self._before_date = _before_date
        self._after_date = _after_date
        self.signal = _Hnsignal
        self.setAutoDelete(True)
    def run(self):
        ...
        #各种方法

    def __del__(self):
        pass

然后将线程类 run内的业务逻辑注释,只保留print(带入的参数)如_suplier

最后程序成功运行。

此时我解决了第一步。因为后面重新取消注释业务逻辑后,程序依旧死亡。

于是我加了线程锁,添加了PyQt5自带的QMutex

代码如下:

from PyQt5.QtCore import QRunnable,QThreadPool,QThread

def method():
    '''
        线程池添加且运行线程内容
    '''
    threadpool = QThreadPool()
    threadpool.globalInstance()
    threadpool.setMaxThreadCount(5)
    for i in item:    #item为QTable类内容
        fs = thfs
        bedate = mbedate
        aferdate = mafdate
        signal = threadsignal
        mythread = HnThreadForExcel_Argv(i.text(),fs,bedate,aferdate,signal)
        threadpool.start(i)
    
class HnThreadForExcel_Argv(QRunnable):
    '''
        为Excel生成提供PYQT线程类
    '''
        qmutex =  QMutex() #加静态锁
    def __init__(self,_suplier,dfs,_before_date,_after_date,_Hnsignal=None):
        super().__init__()
        self._suplier =_suplier
        self.fs = dfs
        self._before_date = _before_date
        self._after_date = _after_date
        self.signal = _Hnsignal
        self.setAutoDelete(True)
    def run(self):
        qmutex.lock()
        ...
        #各种方法
        qmutex.unlock()

    def __del__(self):
        pass

程序依旧卡死,此时我开始怀疑人生,不知道是否是自己架构问题

后来,我通过阅读一位大佬的多线程构建过程

https://blog.csdn.net/qq_40658762/article/details/104879919

其代码的核心含义是以一条线程 管理着线程池,线程池再统一管理着其他线程。

此时我开始怀疑是否和Python PyQt5本身有关,他们自身本质是不存在多线程概念的,绕不开GIL的坑。为了验证这个想法,我决定重新构建一下代码并最终呈现如下:


class HnQtPoolThread(QThread):
    '''
    线程池线程,用于管理线程池
    '''
    def __init__(self):
        super().__init__()
        self.threadpool = HnQTThreadPool()
    def run(self):
        '''
        启动线程池
        '''
        self.threadpool.Start()

    def addThread(self,_thread):
        self.threadpool.addThread(_thread)

class HnQTThreadPool():
    '''
        线程池
    '''
    thread_list = []

    def __init__(self):
        super().__init__()
        self.threadpool = QThreadPool()
        self.threadpool.globalInstance()
        poolnum = tool.loadJsons(tool.setting_address)[0]['pool_num']
        self.threadpool.setMaxThreadCount(int(poolnum))
    
    def addThread(self,_thread):
        self.thread_list.append(_thread)
    def Start(self):
        for i in self.thread_list:
            self.threadpool.start(i)
        self.threadpool.waitForDone()
        self.thread_list.clear()

class HnSignal(QObject):
    progress_signal = pyqtSignal(int)
    result_signal = pyqtSignal(str)
    def __init__(self):
        super().__init__()


class HnThreadForExcel_Argv(QRunnable):
    '''
        为Excel生成提供PYQT线程类
    '''

    def __init__(self,_suplier,dfs,_before_date,_after_date,_Hnsignal=None):
        super().__init__()
        self._suplier =_suplier
        self.fs = dfs
        self._before_date = _before_date
        self._after_date = _after_date
        self.signal = _Hnsignal
        self.setAutoDelete(True)
    def run(self):
        ...
        #各种方法

    def __del__(self):
        pass
if __name__ == '__main__':
    poolthread = HnQtPoolThread()#线程管理线程池

    for i in items:    #PyQt的Table类
        if count % 2 == 0 :
            threadsignal = HnSignal() #PyQt5继承QObject
            thfs = fs
            mbedate = before_date
            mafdate = after_date
            ###为线程创建副本
            mythread = HnThreadForExcel_Argv(i.text(),thfs,mbedate,mafdate,threadsignal)
            threadsignal.progress_signal.connect(progress_bar_callback)
            threadsignal.result_signal.connect(message_call_back)
            try:
                pool.poolthread.addThread(mythread)
            except Exception as e:
                print(e.with_traceback())
        count +=1
    poolthread.start()

程序跑起来了。

总结:

1.可能Python PyQt5本身有关,他们自身本质是不存在多线程概念的,绕不开GIL的坑。解决办法为

以一条线程 管理着线程池,线程池再统一管理着其他线程。代码实现为最后一副代码。或参照

https://blog.csdn.net/qq_40658762/article/details/104879919

2.多线程本身存在着资源竞争的问题,倘若资源有共享情况,需控制资源的利用,如加锁,或者添加副本,代码实现如倒数第二和倒数第三幅

3.最后,程序还可能会遇到 QThread: Destroyed while thread is still running 的问题。我程序出现这问题是由于没有将清空的HnThreadForExcel_Argv(QRunnable)清除。

本项目为Excel按照模板生成工具。可以实现按照模板,取sqlserver数据库数据。

本项目完整版本已上传到GitHub:https://github.com/HONODA/ExcelExport.git

欢迎Star一哈,我还没有一个Star太可怜了好吧

项目目前没有写完整的操作部署文档,有问题的可以评论或者私聊

你可能感兴趣的:(问题,python,python,多线程,pyqt,经验分享,sqlserver)