Control multihilo de QT6 - mutex y semáforo

En los programas, a menudo hay competencia para usar recursos críticos, pero si no se restringen, es probable que ocurran excepciones o resultados inesperados.

Un recurso crítico solo puede ser utilizado por un subproceso a la vez, puede ser una pieza de memoria, una estructura de datos, un archivo o cualquier otra cosa con uso exclusivo.

Estos segmentos de código que deben ejecutarse mutuamente excluyentes se denominan " secciones críticas (Critical Section, CS) ". La sección crítica (segmento de código) implementa operaciones en recursos críticos.Para evitar problemas, solo un subproceso puede ingresar a la sección crítica a la vez.

1. exclusión mutua

Mutex es implementado por QMutex y QMutexLocker .

El propósito de un QMutex es proteger un objeto, una estructura de datos o un fragmento de código para que solo un subproceso pueda acceder a él a la vez (esto es similar a una palabra clave de Java synchronized). Por lo general, es mejor usar mutexes con QMutexLocker, ya que esto facilita garantizar que el bloqueo y el desbloqueo se realicen de manera consistente.

El bloqueo construido por QMutex es QMutex::NonRecursive por defecto, es decir, solo se puede bloquear una vez, si no se puede determinar se puede usar QMutex::try_lock() para obtener el estado actual del bloqueo;

QMutex::QMutex(RecursionMode mode = NonRecursive)

A continuación se demuestra el impacto directo de los bloqueos y los recursos críticos: 

principal.cpp

#include <QCoreApplication>
#include "mythread.h"
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    // creating three thread instances
    MyThread thread1("A"), thread2("B"), thread3("C");

    qDebug() << "hello from GUI thread " << a.thread()->currentThreadId();

    // thread start -> call run()
    thread1.start();
    thread2.start();
    thread3.start();

    return a.exec();
}

mito.h

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QString>

class MyThread : public QThread
{
public:
    // constructor
    // set name and Stop is set as false by default
    MyThread(QString s, bool b = false);

    // overriding the QThread's run() method
    void run();

    // variable that mutex protects
    bool Stop;
private:
    QString name;
};

#endif // MYTHREAD_H

mito.cpp

#include "mythread.h"
#include <QDebug>
#include <QMutex>

MyThread::MyThread(QString s, bool b) : name(s), Stop(b
{
}

// run() will be called when a thread starts
void MyThread::run()
{
    qDebug() << this->name << " " << this->Stop;
    for(int i = 0; i <= 5; i++)
    {
        QMutex mutex;
        // prevent other threads from changing the "Stop" value
        mutex.lock();
        if(this->Stop) break;
        mutex.unlock();
        qDebug() << this->name << " " << i;
    }
}

 QMutexLocker puede simplificar el procesamiento de mutexes, bloquear y desbloquear automáticamente y evitar errores en situaciones complejas.

Como se muestra a continuación: agregue un  mutex QMutex en mythread.h; luego, mythread.cpp es el siguiente, por lo general, solo se usa una instrucción para resolver los complejos problemas de bloqueo y desbloqueo.


void MyThread::run()
{
    qDebug() << this->name << " " << this->Stop;
    for(int i = 0; i <= 5; i++)
    {
        QMutexLocker locker(&mutex);
        if(this->Stop) break;
        qDebug() << this->name << " " << i;
    }
}

2. Cantidad de señal

El semáforo se puede entender como una extensión de la función del mutex, el mutex solo se puede bloquear una vez y el semáforo se puede adquirir varias veces, se puede usar para proteger una cierta cantidad de recursos del mismo tipo.

Un uso típico de un semáforo es controlar un búfer circular compartido entre productores/consumidores.

 qmihilo.cpp

#ifndef QMYTHREAD_H
#define QMYTHREAD_H

//#include    <QObject>
#include    <QThread>
//#include    <QMutex>

class QThreadDAQ : public QThread
{
    Q_OBJECT

private:
    bool    m_stop=false; //停止线程
protected:
    void    run() Q_DECL_OVERRIDE;
public:
    QThreadDAQ();
    void    stopThread();
};

class QThreadShow : public QThread
{
    Q_OBJECT
private:
    bool    m_stop=false; //停止线程
protected:
    void    run() Q_DECL_OVERRIDE;
public:
    QThreadShow();
    void    stopThread();
signals:
    void    newValue(int *data,int count, int seq);
};
#endif // QMYTHREAD_H

qmihilo.h 

#include    "qmythread.h"
#include    <QSemaphore>
//#include    <QTime>

const int BufferSize = 8;
int buffer1[BufferSize];
int buffer2[BufferSize];
int curBuf=1; //当前正在写入的Buffer

int bufNo=0; //采集的缓冲区序号

quint8   counter=0;//数据生成器

QSemaphore emptyBufs(2);//信号量:空的缓冲区个数,初始资源个数为2
QSemaphore fullBufs; //满的缓冲区个数,初始资源为0

QThreadDAQ::QThreadDAQ()
{

}

void QThreadDAQ::stopThread()
{
//    QMutexLocker  locker(&mutex);
    m_stop=true;
}

void QThreadDAQ::run()
{
    m_stop=false;//启动线程时令m_stop=false
    bufNo=0;//缓冲区序号
    curBuf=1; //当前写入使用的缓冲区
    counter=0;//数据生成器

    int n=emptyBufs.available();
    if (n<2)  //保证 线程启动时emptyBufs.available==2
      emptyBufs.release(2-n);

    while(!m_stop)//循环主体
    {
        emptyBufs.acquire();//获取一个空的缓冲区
        for(int i=0;i<BufferSize;i++) //产生一个缓冲区的数据
        {
            if (curBuf==1)
                buffer1[i]=counter; //向缓冲区写入数据
            else
                buffer2[i]=counter;
            counter++; //模拟数据采集卡产生数据

            msleep(20); //每50ms产生一个数
        }

        bufNo++;//缓冲区序号
        if (curBuf==1) // 切换当前写入缓冲区
          curBuf=2;
        else
          curBuf=1;

        fullBufs.release(); //有了一个满的缓冲区,available==1
    }
    quit();
}

void QThreadShow::run()
{
    m_stop=false;//启动线程时令m_stop=false

    int n=fullBufs.available();
    if (n>0)
       fullBufs.acquire(n); //将fullBufs可用资源个数初始化为0

    while(!m_stop)//循环主体
    {
        fullBufs.acquire(); //等待有缓冲区满,当fullBufs.available==0阻塞

        int bufferData[BufferSize];
        int seq=bufNo;

        if(curBuf==1) //当前在写入的缓冲区是1,那么满的缓冲区是2
            for (int i=0;i<BufferSize;i++)
               bufferData[i]=buffer2[i]; //快速拷贝缓冲区数据
        else
            for (int i=0;i<BufferSize;i++)
               bufferData[i]=buffer1[i];

        emptyBufs.release();//释放一个空缓冲区
        emit    newValue(bufferData,BufferSize,seq);//给主线程传递数据
    }
    quit();
}

QThreadShow::QThreadShow()
{

}

void QThreadShow::stopThread()
{
//    QMutexLocker  locker(&mutex);
    m_stop=true;
}

 diálogo.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include    <QTimer>

#include    "qmythread.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

private:
    QThreadDAQ   threadProducer;
    QThreadShow   threadConsumer;
protected:
    void    closeEvent(QCloseEvent *event);
public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

private slots:
    void    onthreadA_started();
    void    onthreadA_finished();

    void    onthreadB_started();
    void    onthreadB_finished();

    void    onthreadB_newValue(int *data, int count, int bufNo);


    void on_btnClear_clicked();

    void on_btnStopThread_clicked();

    void on_btnStartThread_clicked();

private:
    Ui::Dialog *ui;
};

#endif // DIALOG_H

 diálogo.cpp

#include "dialog.h"
#include "ui_dialog.h"

void Dialog::closeEvent(QCloseEvent *event)
{//窗口关闭
    if (threadProducer.isRunning())
    {
        threadProducer.terminate();//结束线程的run()函数执行
        threadProducer.wait();//
    }

    if (threadConsumer.isRunning())
    {
        threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束
        threadConsumer.wait();//
    }

    event->accept();
}

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);

    connect(&threadProducer,SIGNAL(started()),this,SLOT(onthreadA_started()));
    connect(&threadProducer,SIGNAL(finished()),this,SLOT(onthreadA_finished()));

    connect(&threadConsumer,SIGNAL(started()),this,SLOT(onthreadB_started()));
    connect(&threadConsumer,SIGNAL(finished()),this,SLOT(onthreadB_finished()));

    connect(&threadConsumer,SIGNAL(newValue(int*,int,int)),
            this,SLOT(onthreadB_newValue(int*,int,int)));
}

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

void Dialog::onthreadA_started()
{
    ui->LabA->setText("Thread Producer状态: started");
}

void Dialog::onthreadA_finished()
{
    ui->LabA->setText("Thread Producer状态: finished");
}

void Dialog::onthreadB_started()
{
    ui->LabB->setText("Thread Consumer状态: started");
}

void Dialog::onthreadB_finished()
{
    ui->LabB->setText("Thread Consumer状态: finished");
}

void Dialog::onthreadB_newValue(int *data, int count, int bufNo)
{ //读取threadConsumer 传递的缓冲区的数据
    QString  str=QString::asprintf("第 %d 个缓冲区:",bufNo);
    for (int i=0;i<count;i++)
    {
        str=str+QString::asprintf("%d, ",*data);
        data++;
    }
    str=str+'\n';

    ui->plainTextEdit->appendPlainText(str);
}

void Dialog::on_btnClear_clicked()
{
    ui->plainTextEdit->clear();
}

void Dialog::on_btnStopThread_clicked()
{//结束线程
//    threadConsumer.stopThread();//结束线程的run()函数执行
    threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束
    threadConsumer.wait();//

    threadProducer.terminate();//结束线程的run()函数执行
    threadProducer.wait();//

    ui->btnStartThread->setEnabled(true);
    ui->btnStopThread->setEnabled(false);
}

void Dialog::on_btnStartThread_clicked()
{//启动线程
    threadConsumer.start();
    threadProducer.start();

    ui->btnStartThread->setEnabled(false);
    ui->btnStopThread->setEnabled(true);
}

 

 Como se puede ver en la figura, no hay pérdida de búfer o puntos de datos, y la coordinación entre los dos subprocesos es muy buena.No hay problema para ajustar el tiempo de retraso de la frecuencia de muestreo analógica en la función de ejecución a 2 milisegundos. (la configuración normal es de 50 milisegundos) En la adquisición de datos real, para asegurarse de que no se pierda ningún búfer o puntos de datos, la velocidad del subproceso de lectura de datos debe ser más rápida que la velocidad del subproceso que escribe los datos en el búfer.

 //El código fuente del semáforo se ha subido como un paquete comprimido, que se puede descargar en la parte superior del artículo;

Supongo que te gusta

Origin blog.csdn.net/yanchenyu365/article/details/131379595
Recomendado
Clasificación