1. 程式人生 > >在PySide中使用多程序與多執行緒(multiprocess,threading)

在PySide中使用多程序與多執行緒(multiprocess,threading)

在UI介面程式中,使用到多程序與多執行緒是很常見的場景,有時候我們需要將一些耗時的操作放在其他的執行緒或者程序中,避免卡死主執行緒。而且利用多執行緒加Qt的訊號槽機制我們可以在子程序中實現事件監聽,實時監測程序間的通訊。之前一直對執行緒和程序的理解不太深刻,藉著這次機會好好理解了一下多執行緒與多程序,等之後在接觸帶鎖的多執行緒模式。

multiprocess是python的一個庫,由於python的跨平臺特性,我們可以很容易在不同平臺中使用多執行緒,而不用關心底層的實現。對於程序間的通訊有Queue和Pipe兩種方法,具體可檢視python的官方文件multiprocess

示例程式碼如下,我們在main函式中生成一個訊息佇列,並啟動主介面的事件迴圈,然後我們將button的click訊號與開啟一個子程序的槽函式繫結,並利用另一個訊號槽給訊息佇列中填充資料。此時我們需要在子程序中實現一個事件監聽,來實時接收訊號,實現方式是在子程序中開一個執行緒,然後啟動一個死迴圈來監聽佇列訊息,以此實現實時接收訊號。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import threading as th
import multiprocessing as mp
import quite
from PySide.QtCore import Signal,QObject,QCoreApplication,QEvent

__author__ = 'WingC'

class SignalWrapper(QObject):
    signal = Signal(object)

class main_ui(quite.DialogUiController):
def __init__(self): super().__init__(ui_file='untitled.ui') self.q = mp.Queue() #開啟子程序 self.button('test').clicked.connect(self.start_slave_ui) #主程序傳送資料 self.button('test2').clicked.connect(self.input) def start_slave_ui(self): process = mp.Process(target=slave_ui.main,args=(self.q,)) process.start() def
input(self):
value = self.__get_widget__('edit','test').toPlainText() self.q.put(value) class slave_ui(quite.DialogUiController): def __init__(self,queue): super().__init__(ui_file='receive.ui') self.come_data = SignalWrapper() self.come_data.signal.connect(self.show_data) def check_data(): while True: try: if not queue.empty(): new_data = queue.get() self.come_data.signal.emit(new_data) except BaseException as e: print('# Queue Receiver Exception:', e) if queue is not None: th.Thread(target=check_data,daemon=True).start() def show_data(self,data): self.__get_widget__('edit', 'receive').append(data) @classmethod def main(cls,pipe_receiver: 'mp.Connection'): return cls.class_exec(pipe_receiver) if __name__ == '__main__': main_ui.class_exec()

下面的示例程式碼是Pipe的實現方式,與Queue不同的是Pipe只提供兩個程序之間的通訊,根據引數duplex決定是單向通訊還是全雙工通訊,預設True為全雙工通訊

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import threading as th
import multiprocessing as mp
import quite
from PySide.QtCore import Signal,QObject

__author__ = 'WingC'

class SignalWrapper(QObject):
    signal = Signal(object)

class main_ui(quite.DialogUiController):
    def __init__(self):
        super().__init__(ui_file='send.ui')
        self.receiver,self.sender = mp.Pipe(duplex=False)

        #開啟子程序
        self.button('test').clicked.connect(self.start_slave_ui)
        #主程序傳送資料
        self.button('test2').clicked.connect(self.input)

    def start_slave_ui(self):
        process = mp.Process(target=slave_ui.main,args=(self.receiver,))
        process.start()

    def input(self):
        value = self.__get_widget__('edit','test').toPlainText()
        self.sender.send(value)

class slave_ui(quite.DialogUiController):
    def __init__(self,pipe_receiver):
        super().__init__(ui_file='receive.ui')
        self.come_data = SignalWrapper()
        self.come_data.signal.connect(self.show_data)
        def check_data():
            while True:
                try:
                    new_data = pipe_receiver.recv()
                    self.come_data.signal.emit(new_data)               
                except BaseException as e:
                    print('# Queue Receiver Exception:', e)
        if pipe_receiver is not None:
            th.Thread(target=check_data,daemon=True).start()

    def show_data(self,data):
        self.__get_widget__('edit', 'receive').append(data)

    @classmethod
    def main(cls,pipe_receiver: 'mp.Connection'):
        return cls.class_exec(pipe_receiver)


if __name__ == '__main__':
    main_ui.class_exec()

在Python中還有一個subprocess模組,可以讓我們方便地啟動一個子程序,當我們直接使用subprocess啟動子程式時,可使用call方法,相當於在命令列中執行exe程式:

import subprocess

r = subprocess.call(['nslookup', 'www.python.org']) #等同於命令列中輸入nslookup www.python.org
print('Exit code:', r)      #r為程式呼叫的返回值,0表示程式正常執行

當我們需要與子程式間進行通訊時,比如我們的利用C++編寫的程式碼程式將計算結果返回python程式中,我們就可以使用Popen方法,第一個引數為command,是一個list型別,我們可以傳入command引數,如果程式執行時需要帶引數,我們可以把引數組裝成list,例如[‘test.exe’,’debug’],相當於在命令列執行test.exe debug,關鍵字引數有shell,指定是否在shell中執行,stdin,stdout,stderr的引數型別為subprocess.PIPE,相當於對程式的標準輸入輸出建立連線,以此來向子程式傳輸或者接受資料,p.communicate(command),command即向子程式中傳入的引數,例如往程式test.exe中使用p.communicate(b’input’)相當於在命令列執行test.exe後,再輸入input,p.communicate()返回一個pair,(output,error),我們可以得到輸出資訊和錯誤資訊,當我們需要處理輸出資訊時,有時需要對output進行編碼來列印輸出資訊,例如print(output.decode(‘utf-8))。這裡我們需要注意直接呼叫程式並輸入引數和呼叫程式後輸入引數的區別。當我們使用Popen執行程式時,輸入的command和Popen開啟後執行的p.communicate是不同的,前一種相當於在呼叫exe程式時給main函式傳遞引數。後一種相當於啟動了exe程式後在程式中輸入引數,例如在python環境下執行程式碼。

import subprocess

print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'python.org')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)