在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)