线程间的通讯可以由QSemaphore调控,以保证各个线程对同一资源的访问不冲突。但是进程间的协调就不能利用QSemaphore,而要利用QSystemSemaphore。
此外,在同一进程内的各个线程之间可以用信号-槽机制通信,但是进程之间就不可以了。取而代之的是QSharedMemory。下面的两个程序test_process和ProcessClient运行在不同的进程中。前者为主进程,后者为子进程。主进程利用QProcess::start()启动子进程。QProcess::start(QString())的作用与在命令行输入命令类似。start的输入参数可以是一个exe文件的名字。这个exe文件在另一个进程中运行。当主进程结束,exe所在的子进程也随之结束。
先看主进程的代码:
头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QProcess>
#include <qfile.h>
#include <qsystemsemaphore.h>
#include <qsharedmemory.h>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QProcess m_Proc;
QSharedMemory m_mem;
static QSystemSemaphore m_lockSrc;
static QSystemSemaphore m_lockDst;
void init();
QString read();
void write();
public slots:
void OnClickOK(void);
void OnRecvProc(void);
void OnClickSend(void);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
在头文件中,我定义了主进程向子进程写入数据的函数write(),也定义了读出子进程数据的函数read()。在实际应用中,我只用到了write()。
cpp文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <qbuffer.h>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(&m_Proc, SIGNAL(readyRead()), this, SLOT(OnRecvProc()));
QObject::connect(ui->BtnOK, SIGNAL(clicked()), this, SLOT(OnClickOK()));
QObject::connect(ui->BtnSend, SIGNAL(clicked()), this, SLOT(OnClickSend()));
init();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::init()
{
m_mem.setKey(QString("sharedMem"));
if (m_mem.isAttached())
{
m_mem.detach();
}
m_mem.create(1024);
}
QSystemSemaphore MainWindow::m_lockSrc(QString("lockSrc"), 1, QSystemSemaphore::Create);
QSystemSemaphore MainWindow::m_lockDst(QString("lockDst"), 0, QSystemSemaphore::Create);
void MainWindow::OnClickOK(void)
{
QString qstrCmd = ui->lineEdit->text();
m_Proc.start(qstrCmd);
}
void MainWindow::OnClickSend(void)
{
write();
}
void MainWindow::OnRecvProc(void)
{
QByteArray qba = m_Proc.readAll();
QString qstrFeedBack(qba);
ui->textEdit->setText(qstrFeedBack);
}
QString MainWindow::read()
{
QBuffer buffer;
QDataStream in(&buffer);
QString text;
m_mem.lock();
buffer.setData((char*)m_mem.constData(), m_mem.size());
buffer.open(QBuffer::ReadOnly);
in >> text;
m_mem.unlock();
qDebug() << "WriteSharedMemory:: Read:" << text;
return text;
}
void MainWindow::write( )
{
QBuffer buffer;
buffer.open( QBuffer::ReadWrite );
QDataStream out( &buffer );
QString text = ui->lineEdit->text();
out << text;
int size = buffer.size();
if(m_mem.size()<size)
{
qDebug() << "共享内存空间不够!";
return ;
}
if(m_lockSrc.acquire())
{
// Write into the shared memory
m_mem.lock();
char *to = (char*)m_mem.data();
const char *from = buffer.data().data();
memcpy( to, from, qMin( m_mem.size(), size ) );
m_mem.unlock();
m_lockDst.release();
qDebug() << "WriteSharedMemory:: Write:" << text;
}
}
再看子进程。它包括两个类:Client和thrd。本来只要client一个类即可接收主线程发来的数据。但是实验发现那样会很卡顿。所以建立一个QThread的派生类--thrd。thrd负责接收主线程的数据。收到后,再利用信号槽机制传给Client,显示出来。通过开启一个线程的方式避免卡顿。
先看Client头文件:
#pragma once
#include <QWidget>
#include <qlineedit.h>
#include <QResizeEvent>
#include "thrd.h"
class Client : public QWidget
{
Q_OBJECT
public:
Client(QWidget *parent = 0);
~Client();
thrd m_thrd;
QLineEdit * m_pEdt;
public slots:
void OnRecv(QByteArray);
protected:
void resizeEvent(QResizeEvent *);
};
Client.cpp:
#include "client.h"
#include <QDebug>
#include <qbuffer.h>
#include <QMessageBox>
Client::Client(QWidget *parent)
: QWidget(parent)
{
QMessageBox msg;
msg.setText("start");
msg.exec();
m_pEdt = new QLineEdit(this);
QObject::connect(&m_thrd, SIGNAL(sigMsg(QByteArray)), this, SLOT(OnRecv(QByteArray)));
m_thrd.start();
}
Client::~Client()
{
m_thrd.terminate();
}
void Client::OnRecv(QByteArray qba)
{
m_pEdt->setText(QString(qba));
}
void Client::resizeEvent(QResizeEvent *e)
{
m_pEdt->setGeometry(width() / 10, height()/10, width() * 0.8, 20);
}
thrd.h
#pragma once
#include <qthread.h>
#include <qsystemsemaphore.h>
#include <qsharedmemory.h>
class thrd : public QThread
{
Q_OBJECT
public:
thrd(QObject * parent = 0);
~thrd();
static QSystemSemaphore m_lockSrc;
static QSystemSemaphore m_lockDst;
QSharedMemory m_mem;
void read();
signals:
void sigMsg(QByteArray);
protected:
void run();
};
thrd.cpp
#include "thrd.h"
#include <qbuffer.h>
#include <qdatastream.h>
thrd::thrd(QObject * parent) : QThread(parent)
{
m_mem.setKey(QString("sharedMem"));
}
thrd::~thrd()
{
}
QSystemSemaphore thrd::m_lockSrc(QString("lockSrc"), 1, QSystemSemaphore::Open);
QSystemSemaphore thrd::m_lockDst(QString("lockDst"), 0, QSystemSemaphore::Open);
void thrd::run()
{
while(true)
{
read();
msleep(1000);
}
}
void thrd::read()
{
if(m_mem.isAttached())
{
//qDebug() << "ReadSharedMemory:: haved attached.";
}
else
{
if(!m_mem.attach())
{
QSharedMemory::SharedMemoryError m = m_mem.error();
return;
}
else
{
//qDebug() << "ReadSharedMemory:: attach success.";
}
}
QBuffer buffer;
QDataStream in(&buffer);
QString text;
if(m_lockDst.acquire())
{
m_mem.lock();
buffer.setData((char*)m_mem.constData(), m_mem.size());
buffer.open(QBuffer::ReadOnly);
in >> text;
//清空缓存
char* to = (char*)m_mem.data();
memset(to,0,m_mem.size());
m_mem.unlock();
m_lockSrc.release();
QByteArray qba = text.toLatin1();
emit sigMsg(qba);
}
}
运行结果:
先启动test_process.exe。在它的第一个文本框里输入ProcessClient.exe的路径,然后点击OK,启动子进程窗口。随后,你可以在父进程的同一个文本框里输入其他字符,然后点击"send",将内容发给子进程。子进程收到后,会将信息显示在自己的文本框里。在本例中,我发送的消息是12345
在本例子中,主进程给QSharedMemory命名,并且赋予它1024字节的空间。还创建了2个信号量。
子进程的共享内存QSharedMemory必须使用主进程的共享内存一样的名字,并且要使用同名的信号量。但使用时,只要open即可,不需要create。
最后解释为什么要使用两个QSystemSemaphore,而不是一个。在前面的博客<<自己对互斥和同步的理解>>一文中,我那下棋做比方解释同步和互斥。假如我只用一个semaphore,主进程写完一次数据后,可能不会等子进程读取,就会再写一次数据。这是因为系统的调度随机的,不会保证写入-读出严格交替。但是使用了2个semaphore之后,读写彼此牵制,只能一来一去,就保证了同步。