最近在写Excel按照模板生成工具。
使用的环境:python3.7 + PYQT5 + MSsql +xlwings +json
在写完界面和业务逻辑之后,开始尝试写多线程。一开始的思路是在导出时生成一个线程池->线程池分别插入线程并且开始。
这一切都在debug 断点模式 下运行好好的。
代码大致内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 |
在线程池
1 | threadpool.start(i) |
之前通过断点停留,并且再次运行时,程序成功运行。
然而,在程序不打断点情况下直接运行,程序卡死····而且由于线程池卡死关系,在线程类中重新debug下无法捕获到卡死代码位置。
首先想到的问题可能是,程序死锁了。但是如何死锁我并不清楚。通过交友网站基佬们的建议,我设置了线程们的优先权。
果然还是不行。
通过浅薄的知识,我设想多线程可能因为线程之间资源竞争导致程序死锁。
于是我为多个线程添加了副本,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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的坑。为了验证这个想法,我决定重新构建一下代码并最终呈现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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太可怜了好吧
项目目前没有写完整的操作部署文档,有问题的可以评论或者私聊