在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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章