PyQt5 线程管理 解决耗时线程导致假死问题
前言由于项目需求,需要设计一款目标检测后进行后台控制,并在界面上实时显示的上位机。设计的一开始没有考虑到上位机后台功能实现的耗时,功能实现后发现执行某些耗时操作会导致程序界面无响应,进入假死状态,最终采用线程管理解决这一问题。简介PyQt5是Digia的一套Qt5应用框架与python的结合,同时支持python2.x和python3.x。PyQt5由一系列python模块组成,适用于Linu
前言
由于项目需求,需要设计一款目标检测后进行后台控制,并在界面上实时显示的上位机。设计的一开始没有考虑到上位机后台功能实现的耗时,功能实现后发现执行某些耗时操作会导致程序界面无响应,进入假死状态,最终采用线程管理解决这一问题。
简介
PyQt5是Digia的一套Qt5应用框架与python的结合,同时支持python2.x和python3.x。PyQt5由一系列python模块组成,适用于Linux,Windows,Mac OS等主流操作系统。下面简单介绍其安装的方式:
pip install PyQt5
pip install PyQt5-tools
Python 中,有关线程开发的部分被单独封装到了模块中,threading
是Python3之后的线程模块,提供了功能丰富的多线程支持。
threading
主要通过两种方式来创建线程:
- 使用Thread 类的构造器创建线程。即直接对类
threading.Thread
进行实例化创建线程,并调用实例化对象的 start() 方法启动线程。 - 继承Thread 类创建线程类。即用
threading.Thread
派生出一个新的子类,将新建类实例化创建线程,并调用其 start() 方法启动线程。
假死状态
通常我们使用应用时,总是希望其在运行后台操作的同时,又能够在最快的时间内响应用户的操作,这也是市场上大多数应用能够做到的。而当我们自己作为初学者在设计应用时常常容易将注意力集中在功能的实现从而忽略关于了所实现的功能与应用的耦合关系。
这就会造成一个严重的问题,当用户的某个操作需要比较久的计算时间时,由于程序是单线程的设计,界面会呈现出无响应状态,以至于用户认为程序停止运行,降低使用的体验感。这类问题我们也称为应用的假死状态。随着应用功能越来越复杂,高效解决这类问题十分有必要!
解决方法
通过python中的threading库,可以将耗时线程转入后台,避免其操作堵塞主线程,造成应用进入假死状态。同时还要注意,多线程中涉及到的内存的安全问题,可以使用互斥锁解决。
import threading
def task(a, b):
···
thread = threading.Thread(target=task, args=(a, b))
# 把子进程设置为守护线程,必须在start()之前设置
thread.setDaemon(True)
# 开始执行线程
thread.start()
由于项目需求,耗时线程中含有对视频流的获取和目标检测操作。Opencv的VideoCapture
对视频流的读取并不是实时的,尤其是获取间隔时间较长时,会造成帧缓冲区的堆积,并且不能确定Opencv会在何时清理该缓冲区。所以,直接读取不仅会造成程序假死,缓冲区的堆积也会使帧的延迟严重。
这里的解决方法是,将视频流的读取和处理分成两个线程,一个线程不断地读取视频流中的帧缓冲区,另一线程执行处理操作。
import threading
from threading import Lock
import multiprocessing as mp
import cv2
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
queues1 = mp.Queue(maxsize=4)
def cap_put(self, queue):
while True:
queue.put(cap.read()[1])
queue.get() if queue.qsize() > 1 else queue
def cap_get(self, queue):
while True:
img = queue.get()
cv2.imshow("frame", img)
cv2.waitKey(30)
put_thread = threading.Thread(target=cap_put, args=(queue))
# 把子进程设置为守护线程,必须在start()之前设置
put_thread.setDaemon(True)
put_thread.start()
get_thread = threading.Thread(target=cap_get, args=(queue))
get_thread.setDaemon(True) # 把子进程设置为守护线程,必须在start()之前设置
get_thread.start()
如果考虑多线程之间的安全问题,也可以为自己的线程操作上锁,python threading提供了方便的互斥锁操作,如下所示
from threading import Lock
lock1 = Lock()
#加锁
lock1.acquire()
try :
#需要保证线程安全的代码
#...
#使用finally 块来保证释放锁
finally :
#修改完成,释放锁
self.lock.release()
参考
后续
喜欢的话可以关注一下我的公众号技术开发小圈,尤其是对深度学习以及计算机视觉有兴趣的朋友,我会把相关的源码以及更多资料发在上面,希望可以帮助到新入门的大家!
更多推荐
所有评论(0)