写在前面
博主在学习Python编程过程中,有个问题一直没有理解透彻,就是多线程问题,因为工作中的项目都比较小,很少用到多线程,但是这个问题却是个很底层的问题,因此还是打算学习一下,查阅了一些资料,记录一下学习心得。
理解多线程
一般情况下,应用程序都是单线程运行的,但是对于GUI程序,可能一个线程无法满足要求,比如我有两个按钮A和B,我点击了一个按钮A,这个按钮的工作量很大,需要运行很长时间,然后我就把这个按钮的工作任务放到线程里面去,让它后台工作,这个时候,再点击另外一个按钮B,可以让这个按钮B开始工作。
先看看单线程的工作机制。
单线程执行两个按钮
在界面上设置按钮1和按钮2,按钮1打印数字1、2、3、4、5,按钮2打印a、b、c、d、e,我们大概率会这么写
from PyQt5.Qt import (QApplication, QWidget, QPushButton, QThread, QMutex, pyqtSignal)
import sys
import time
class Demo(QWidget):
def __init__(self):
super().__init__()
self.btn1 = QPushButton("按钮1", self)
self.btn1.move(120, 80)
self.btn1.clicked.connect(self.click1)
self.btn2 = QPushButton("按钮2", self)
self.btn2.move(120, 120)
self.btn2.clicked.connect(self.click2)
def click1(self):
values = [1, 2, 3, 4, 5]
for i in values:
print(i)
time.sleep(1)
def click2(self):
values = ["a", "b", "c", "d", "e"]
for i in values:
print(i)
time.sleep(1)
if __name__ == "__main__":
app = QApplication(sys.argv)
mywin = Demo()
mywin.show()
sys.exit(app.exec_())
看看运行情况
如果我点击按钮1后马上又点击按钮2,程序会出现假死的情况,必须得等按钮1的任务执行完才能开始按钮2的任务,这样也挺浪费资源的,所以这里考虑用多线程来解决这个问题。
多线程任务
现在考虑把这两个按钮的工作都放到线程里面去,来看看具体是怎么实施的,先上代码
# -*- coding: utf-8 -*-
"""
Created on Thu May 28 14:20:25 2020
@author: HUANG Gang
"""
from PyQt5.Qt import (QApplication, QWidget, QPushButton, QThread, QMutex, pyqtSignal)
import sys
import time
class Thread1(QThread):
def __init__(self):
super().__init__()
def run(self):
values = [1, 2, 3, 4, 5]
for i in values:
print(i)
time.sleep(1)
class Thread2(QThread):
def __init__(self):
super().__init__()
def run(self):
values = ["a", "b", "c", "d", "e"]
for i in values:
print(i)
time.sleep(1)
class Demo(QWidget):
def __init__(self):
super().__init__()
self.btn1 = QPushButton("按钮1", self)
self.btn1.move(120, 80)
self.btn1.clicked.connect(self.click1)
self.btn2 = QPushButton("按钮2", self)
self.btn2.move(120, 120)
self.btn2.clicked.connect(self.click2)
def click1(self):
self.thread1 = Thread1()
self.thread1.start()
def click2(self):
self.thread2 = Thread2()
self.thread2.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
mywin = Demo()
mywin.show()
sys.exit(app.exec_())
在创建应用程序之前,我把这两个按钮的工作任务写到两个独立的线程中去了,然后在界面程序里面实例化这两个线程,来看看执行情况
注意控制台的输出,在加入了这两个线程后,并未出现卡死的情况,线程之间不会相互干扰,但是有个问题,如果按钮1的线程任务完成之前重新点击了按钮1,那么这两个指令之间会产生干扰,就是按钮1的任务会刷新。这不是我希望发生的事情。
解决这个问题要用到线程锁。
线程锁
线程锁相对来说容易理解一点,就是在线程开始工作时,加上一把锁,在任务结束后,方可解锁,这样就可以保证整个任务是连续的,看代码
# -*- coding: utf-8 -*-
"""
Created on Thu May 28 14:20:25 2020
@author: HUANG Gang
"""
from PyQt5.Qt import (QApplication, QWidget, QPushButton, QThread, QMutex, pyqtSignal)
import sys
import time
qmut1 = QMutex()
qmut2 = QMutex()
class Thread1(QThread):
def __init__(self):
super().__init__()
def run(self):
qmut1.lock() # 线程加锁
values = [1, 2, 3, 4, 5]
for i in values:
print(i)
time.sleep(1)
qmut1.unlock() # 线程解锁
class Thread2(QThread):
def __init__(self):
super().__init__()
def run(self):
qmut2.lock() # 线程加锁
values = ["a", "b", "c", "d", "e"]
for i in values:
print(i)
time.sleep(1)
qmut2.unlock() # 线程解锁
class Demo(QWidget):
def __init__(self):
super().__init__()
self.btn1 = QPushButton("按钮1", self)
self.btn1.move(120, 80)
self.btn1.clicked.connect(self.click1)
self.btn2 = QPushButton("按钮2", self)
self.btn2.move(120, 120)
self.btn2.clicked.connect(self.click2)
def click1(self):
self.thread1 = Thread1()
self.thread1.start()
def click2(self):
self.thread2 = Thread2()
self.thread2.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
mywin = Demo()
mywin.show()
sys.exit(app.exec_())
在程序开始执行之前,先实例化两个线程锁,这两把锁分别加在两个线程之中,看看程序执行情况。
看看控制台的输出,1/2/3/4/5和a/b/c/d/e的输出是相对连续的,当然两个按钮之间还是会相互交叉打印,这也是多线程的精髓。
至此,我大概理解了多线程的使用方法,后面就可以进行更复杂的应用了。
完