最近在写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太可怜了好吧
项目目前没有写完整的操作部署文档,有问题的可以评论或者私聊