My QT Creator learning notes - processes and threads

Posted by ghadacr on Thu, 27 Jan 2022 14:56:05 +0100

Reference: Qt Creator quick start, Third Edition, edited by Huo Yafei

1, Process

1.1 process related classes, interfaces, signals, etc

In the current application, you can call external programs to realize some functions, which will use the process. Qt's qpprocess class is used to start and communicate with an external program (process).

To start a process, you can use the qpprocess:: start() function to take the program name and the command line parameters to run the program as the parameters of the function.

After executing the start() function, qpprocess enters the Starting state. When the program has been run, qpprocess will enter the Running state and send the started() signal. When the program exits, qpprocess re enters NotRunning state (initial state) and transmits finished() signal. The finished() information is emitted, which provides the exit code and exit status of the process. You can also call exitCode() to view the type of error and the last error. Use exitState() to view the status of the current process.

Qpprocess allows you to think of a process as a sequential I/O device. You can call standard inputs and outputs such as write(), read()\readLine() and getChar() to read and write.

1.2 start a notepad process and display examples of various states of the process

The function of this example is to click the button to start the Notepad process and open a Notepad document on the desktop.

Associate the readyread, statechanged, erroreoccurred and finished signals of qpprocess to the user-defined slot. In the user-defined slot, each status of the process is displayed.

Header file source code

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QProcess>
#include <QSharedMemory>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void showResult();
    void showState(QProcess::ProcessState);
    void showError();
    void showFinish(int,QProcess::ExitStatus);

private:
    Ui::MainWindow *ui;
    QProcess myProcess;
};
#endif // MAINWINDOW_H

cpp source code

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QTextCodec>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(&myProcess,&QProcess::readyRead,this,&MainWindow::showResult);
    connect(&myProcess,&QProcess::stateChanged,this,&MainWindow::showState);
    connect(&myProcess,&QProcess::errorOccurred,this,&MainWindow::showError);
    connect(&myProcess,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(showFinish(int,QProcess::ExitStatus)));
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
    QString program="notepad.exe";
    QStringList arguments;
    arguments<<"C:/Users/Administrator/Desktop/processtest.txt";
    myProcess.start(program,arguments);
}

void MainWindow::showResult()
{
    QTextCodec* codec=QTextCodec::codecForLocale();
    qDebug()<<"showResult:"<<endl<<codec->toUnicode(myProcess.readAll());
}

void MainWindow::showState(QProcess::ProcessState state)
{
    qDebug()<<"showState:";
    if(state==QProcess::NotRunning)
    {
        qDebug()<<"Not Running";
    }
    else if(state==QProcess::Starting)
    {
        qDebug()<<"Not Starting";
    }
    else
    {
        qDebug()<<"Running";
    }
}

void MainWindow::showError()
{
    qDebug()<<"showError:"<<endl<<myProcess.errorString();
}

void MainWindow::showFinish(int exitCode, QProcess::ExitStatus exitStatus)
{
    qDebug()<<"showFinished:"<<endl<<exitCode<<exitStatus;
}

Click the button to run. The effect is as follows: open myprocess The Notepad document specified by the parameter start().

At the same time, each state of the process is output, as shown in the following figure

After closing Notepad, the console outputs Not Running,showFinished and exit status, as shown in the following figure.

1.3 interprocess communication

Qt provides a variety of methods to implement inter process communication IPC (inter process communication) in Qt applications:

1)TCP/IP

The cross platform Qt Network module provides many classes to realize network programming. It provides high-level classes (such as QNetworkAccessManager) to use the specified application level protocols, and lower level classes (such as QTcpSocket, QTcpServer and QSslSocket) to implement relevant protocols.

2) Shared memory

Qssharedmemory is a cross platform shared memory class, which provides the implementation of accessing the shared memory of the operating system. It allows multiple threads and processes to safely access shared memory segments.

3)D-Bus

D-Bus module is a Unix library, which can use D-Bus protocol to realize inter process communication. It extends the signal and slot mechanism of Qt to the IPC level, allowing the signal transmitted from one process to be associated with the slot of another process

4 )QProcess

5) Session management

On Linux/X11 platform, Qt provides support for session management, which allows events to propagate to processes.

1.4. Use shared memory to realize interprocess communication example code

The function of this example is to start two applications (i.e. two processes are started). If one program clicks the "load picture from file" button, the picture will be loaded into the shared memory and displayed on the label at the same time. In another application (another process), click the "load picture from memory" button to read the picture from the shared memory and display it on the label. In this way, the communication between the two processes is realized.

Several important interfaces:

1) Before using shared memory, you need to specify a key, which is used by the system as the flag of the underlying shared memory side.

See sharedmemory in the source constructor below setKey("QSharedMemoryExample"); code.

2) Call the isATTached() function to determine whether the process is connected to the shared memory segment.

3) Use the deatch() interface to separate the process from the shared memory segment.

4) Use the create() function to create a shared memory segment of the specified size in bytes. The function will also automatically connect the shared memory segment to the process.

5) When performing operations on shared memory segments, lock them first, that is, call the lock() function. After the operation is completed, call the unlock() function to unlock.

6) Use the attach function to connect a process to a shared memory segment.

2) ~ 5) see the source loadFromFile function below.

6) See the source loadFromMemory function below.

Header file code

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QSharedMemory>

QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE

class Dialog : public QDialog
{
    Q_OBJECT

public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();
public slots:
    void loadFromFile();
    void loadFromMemory();
private slots:
    void on_loadFromFileButton_clicked();

    void on_loadFromSharedMemoryButton_clicked();

private:
    void detach();
private:
    Ui::Dialog *ui;
    QSharedMemory sharedMemory;
};
#endif // DIALOG_H

Source file code

#include "dialog.h"
#include "ui_dialog.h"
#include <QFileDialog>
#include <QBuffer>
#include <QDebug>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)
{
    ui->setupUi(this);
    sharedMemory.setKey("QSharedMemoryExample");

}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::loadFromFile()
{
    if(sharedMemory.isAttached())
        detach();
    ui->label->setText(QString::fromLocal8Bit("Select a picture file!"));
    QString fileName=QFileDialog::getOpenFileName(0,QString(),QString(),tr("Image(*.png *.jpg"));
    QImage image;
    if(!image.load(fileName))
    {
        ui->label->setText(QString::fromLocal8Bit("The selected file is not a picture, please select a picture file!"));
        return;
    }
    ui->label->setPixmap(QPixmap::fromImage(image));
    //Load pictures into shared memory
    QBuffer buffer;
    buffer.open(QBuffer::ReadWrite);
    QDataStream out(&buffer);
    out<<image;
    int size=buffer.size();
    if(!sharedMemory.create(size))
    {
        ui->label->setText(QString::fromLocal8Bit("Cannot create shared memory segment!"));
        return;
    }
    sharedMemory.lock();
    char* to=(char*)sharedMemory.data();
    const char* from=buffer.data().data();
    memcpy(to,from,qMin(sharedMemory.size(),size));
    sharedMemory.unlock();
}

void Dialog::loadFromMemory()
{
    if(!sharedMemory.attach())
    {
        ui->label->setText(QString::fromLocal8Bit("Unable to connect to shared memory fragment\n Please load a picture first!"));
        return;
    }
    QBuffer buffer;
    QDataStream in(&buffer);
    QImage image;
    sharedMemory.lock();
    buffer.setData((char*)sharedMemory.constData(),sharedMemory.size());
    buffer.open(QBuffer::ReadOnly);
    in>>image;
    sharedMemory.unlock();
    sharedMemory.detach();
    ui->label->setPixmap(QPixmap::fromImage(image));
}

void Dialog::detach()
{
    if(!sharedMemory.detach())
        ui->label->setText(QString::fromLocal8Bit("Cannot detach from shared memory!"));
}


void Dialog::on_loadFromFileButton_clicked()
{
    loadFromFile();
}

void Dialog::on_loadFromSharedMemoryButton_clicked()
{
    loadFromMemory();
}

The operation effect is as follows: click "load picture from file" on the left and click "display picture from share" on the right

2, Thread

Qt provides support for threads, including a set of platform independent thread classes, a thread safe way to send events, and cross thread signal slot Association. These make it easy to develop portable multithreaded Qt applications and make full use of multiprocessor machines.

2.1 starting threads using QThread

Qtthread class in Qt provides platform independent threads. A QThread instance represents a thread that can be controlled independently in the application. QThread starts from the run() function. The default run starts the event loop by calling exec, and runs a Qt event loop in the thread. To create a thread, you need to subclass QThread and re implement the run function.

Call the start() function to start executing a thread, and start() calls the run() function by default. When returned from the run function, the thread execution ends. QThread will emit signals such as started(),finished() and terminated() at the beginning, end and termination. You can also use isFinished() and isRunning() to query the status of threads. You can use wait() to block until the thread ends.

The static functions currentThreadId() and currentThread() can return the identifier of the currently executing thread. The former returns a specific ID and the latter returns a QThread pointer.

2.2 synchronization thread

The QMutex, QReadWriteLock, QSemaphore, and QWaitCondition classes in Qt provide methods for synchronizing threads.

QMutex provides a mutex that can be obtained by at most one thread at any time. Mutexes are often used to protect access to shared data.

QReadWriteLock is a read-write lock, which is very similar to QMutex, but its access to shared data is divided into "read" access and "write" access, allowing multiple threads to "read" access to data at the same time.

QSemaphore, or semaphore, is a generalization of QMutex. It is used to protect a certain number of the same resources, while mutex can only protect one resource.

QWaitCondition is a condition variable that allows a thread to wake up other threads when some conditions are met. One or more threads can be blocked to wait for a QWaitCondition to set a condition for wakeOne() or wakeAll().

2.3 example of thread synchronization using semaphores

This example demonstrates how to use the QSemaphore semaphore to protect access to the ring buffer shared by producer and consumer threads. The producer writes data to the buffer until it reaches the end of the buffer. At this time, it will restart from the starting point and overwrite the existing data. The consumer thread reads the generated data and outputs it.

The code is as follows

#include <QtCore>
#include <stdio.h>
#include <stdlib.h>
#include <QDebug>
#include <QThread>

const int DataSize=10;
const int BufferSize=5;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore useBytes;

class Producer : public QThread
{
public:
    void run();
};
void Producer::run()
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    for(int i=0;i<DataSize;i++)
    {
        freeBytes.acquire();
        buffer[i%BufferSize]="ACGT"[(int)qrand()%4];
        qDebug()<<QString("producer:%1").arg(buffer[i%BufferSize]);
        useBytes.release();
    }
}

class Customer : public QThread
{
public:
    void run();
};

void Customer::run()
{
    for(int i=0;i<DataSize;i++)
    {
        useBytes.acquire();
        qDebug()<<QString("customer:%1").arg(buffer[i%BufferSize]);
        freeBytes.release();
    }
}

int main(int argc,char* argv[])
{
    QCoreApplication app(argc,argv);
    Producer producer;
    Customer customer;
    producer.start();
    customer.start();
    producer.wait();
    customer.wait();
    return app.exec();
}

In order to synchronize producers and consumers, the above code needs to use two semaphores. The freeBytes semaphore controls the free area of the buffer. The useBytes semaphore controls the buffer area that has been used.

QSemaphore::acquire() function is used to obtain a certain number of resources. One resource is obtained by default. Use the QSemaphore::release() function to release the semaphore.

Program execution process: at first, only the producer thread can execute, and the consumer thread is blocked to wait for the useBytes semaphore to be released. Once the producer puts a byte into the buffer, the available size of freeBytes, that is, freeBytes Available() returns BufferSize-1, and useBytes Avaiable() returns 1. Two things can happen: either the consumer thread reads this byte, or the producer thread produces a second byte. The execution order of two threads cannot be determined.

The program operation structure is as follows

2.4 reentrant and thread safety

1) a thread safe function can be called by multiple threads at the same time, even if they use shared data. Because all the real columns of the shared data are serialized.

2) a reentrant function can also be called by multiple threads at the same time, but only when each call uses its own data.

Generally speaking, for a class, if it can be used in multiple threads, and each thread calls a different real column, the class is reentrant. If multiple threads use the same instance, and the member functions of this class can also be called safely by multiple threads, this class is called thread safe.

 

Topics: Qt UI QtCreator