前言
由於專案需求,需要設計一款目標檢測後進行後臺控制,並在介面上實時顯示的上位機。設計的一開始沒有考慮到上位機後臺功能實現的耗時,功能實現後發現執行某些耗時操作會導致程式介面無響應,進入假死狀態,最終採用執行緒管理解決這一問題。
簡介
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()