Qt QQueue fila multiencadeada segura, fila de bloqueio

1. Uso básico da fila C++

Em C++, queue é uma classe modelo usada para implementar a estrutura de dados da fila, seguindo o princípio do primeiro a entrar, primeiro a sair.

♦ Métodos comuns: ·

queue<int> Q;            //定义一个int型队列
Q.empty();               //返回队列是否为空
Q.size();                //返回当前队列长度
Q.front();               //返回当前队列的第一个元素
Q.back();              	 //返回当前队列的最后一个元素
Q.push();                //在队列后面插入一个元素, 比如插入数字5: Q.push(5)
Q.pop();                 //从当前队列里,移出第一个元素

♦ Fácil de usar: ·

#include <iostream>
#include <queue>

using namespace std;

int main()
{
    
    
    // 创建一个queue对象
    queue<int> Q;

    // 向队列中添加元素
    Q.push(1);
    Q.push(2);
    Q.push(3);

    cout<<"queue empty?  "<<Q.empty()<<endl;
    cout<<"queue size:   "<<Q.size()<<endl;

    // 从队列中移除元素,并输出
    while (!Q.empty()) {
    
    
        int value = Q.front();
        Q.pop();
        cout << "Dequeued:" << value << endl;
    }

    return 0;
}

♦ Impressão: ·

insira a descrição da imagem aqui

2. Uso básico da fila Qt QQueue

Herança QQueue e QList

♦ Métodos comuns: ·

QQueue<int> QQ;         	 //定义一个int型队列

QQ.isEmpty();                //返回队列是否为空

QQ.size();                   //返回队列元素个数

QQ.clear();                  //清空队列

QQ.enqueue();                //在队列尾部添加一个元素, 比如插入数字5: QQ.enqueue(5)
/* 相当于
Q.push();                 
*/

QQ.dequeue();                //删除当前队列第一个元素,并返回这个元素
/* 相当于
Q.front();                   //返回当前队列的第一个元素
Q.pop();                     //从当前队列里,移出第一个元素
*/

QQ.head();                   //返回当前队列第一个元素
/* 相当于
Q.front();                   
*/

QQ.last();                   //返回当前队列尾部的元素
/* 相当于
Q.back();              	   
*/
T &  operator[]( int i );   //以数组形式访问队列元素

♦ Exemplos: ·

#include <QCoreApplication>
#include <QQueue>
#include <QDebug>

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);

    // 创建一个QQueue对象
    QQueue<int> QQ;

    // 向队列中添加元素
    QQ.enqueue(1);
    QQ.enqueue(2);
    QQ.enqueue(3);

    qDebug()<<"queue empty:  "<<QQ.isEmpty();
    qDebug()<<"queue size:  " <<QQ.size();
    qDebug()<<"queue head:  " <<QQ.head()  ;
    qDebug()<<"queue last:  " <<QQ.last()  << "\n";


    // 从队列中移除元素,并输出
    while (!QQ.isEmpty()) {
    
    
        int value = QQ.dequeue();
        qDebug() << "Dequeued:" << value;
    }

    return a.exec();
}

♦ Impressão: ·

insira a descrição da imagem aqui

3. Qt QQueue fila multithreaded

Na programação multiencadeada, como QQueue não é thread-safe, precisamos usar um mutex (QMutex) para proteger a fila primeiro. Ao ler e escrever na fila, precisamos adquirir o bloqueio do mutex para evitar problemas de competição de dados causados ​​por vários threads acessando a fila ao mesmo tempo.

Em seguida, observe um programa de amostra simples por meio de produtores e consumidores clássicos, demonstrando como usar QQueue para implementar uma fila segura de threads:

#include <QCoreApplication>
#include <QQueue>
#include <QMutex>
#include <QThread>
#include <QDebug>

// 定义线程安全队列类
template<typename T>
class ThreadSafeQueue
{
    
    
public:
    // 添加元素到队列尾部
    void enqueue(const T& value) {
    
    
        QMutexLocker locker(&m_mutex);
        m_queue.enqueue(value);
    }

    // 从队列头部移除一个元素,并返回它
    T dequeue() {
    
    
        QMutexLocker locker(&m_mutex);
        if (m_queue.isEmpty()) {
    
    
            return T();
        }
        return m_queue.dequeue();
    }

    // 返回队列是否为空
    bool isEmpty() const {
    
    
        QMutexLocker locker(&m_mutex);
        return m_queue.isEmpty();
    }

private:
    QQueue<T> m_queue;
    mutable QMutex m_mutex;
};

// 定义生产者线程类
class ProducerThread : public QThread
{
    
    
public:
    ProducerThread(ThreadSafeQueue<int>& queue)
        : m_queue(queue)
    {
    
    
    }

protected:
    void run() override {
    
    
        for (int i = 1; i <= 10; i++) {
    
    
            m_queue.enqueue(i);
            qDebug() << "Enqueued:" << i;
            msleep(500);
        }
    }

private:
    ThreadSafeQueue<int>& m_queue;
};

// 定义消费者线程类
class ConsumerThread : public QThread
{
    
    
public:
    ConsumerThread(ThreadSafeQueue<int>& queue)
        : m_queue(queue)
    {
    
    
    }

protected:
    void run() override {
    
    
        while (!isInterruptionRequested()) {
    
    
            if (!m_queue.isEmpty()) {
    
    
                int value = m_queue.dequeue();
                qDebug() << "Dequeued:" << value;
            }
            msleep(500);
        }
    }

private:
    ThreadSafeQueue<int>& m_queue;
};

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);

    // 创建线程安全队列对象
    ThreadSafeQueue<int> queue;

    // 创建生产者线程对象和消费者线程对象
    ProducerThread producer(queue);
    ConsumerThread consumer(queue);

    // 启动线程
    producer.start();
    consumer.start();

    qDebug() << "F1";

    // 等待线程结束
    //在producer等待期间,  consumer run()也会运行 直到producer 运行完毕,main才能往下执行
    producer.wait();

    qDebug() << "F2";

    consumer.requestInterruption(); //相当于 ctrl + c 结束 consumer 线程
    qDebug() << "F3";
    consumer.wait();
    qDebug() << "F4";

    return a.exec();
}

♦ Resultado da corrida:

insira a descrição da imagem aqui

No programa de exemplo acima, primeiro definimos uma classe de modelo ThreadSafeQueue, que é usada para implementar uma fila thread-safe. Essa classe usa um QMutex para proteger o objeto QQueue para segurança do encadeamento.

Em seguida, definimos duas classes de encadeamento ProducerThread e ConsumerThread para produzir e
consumir dados.

No ProducerThread, adicionamos elementos à fila em um loop, adicionando um elemento a cada 500 milissegundos. No ConsumerThread, retiramos os elementos da fila, retirando um elemento a cada 500 milissegundos. Ao retirar elementos, precisamos julgar se a fila está vazia para evitar situações anormais.

Entre eles: quando o produtor.wait() for executado , o thread principal em mian será bloqueado, o produtor não acordará até que o produtor termine a execução, o thread do consumidor não será afetado;

E se a velocidade de processamento de dados não corresponder?

  • O produtor dorme por 500ms e o consumidor dorme por 500ms, que é a situação acima
  • Tempo de suspensão do produtor < tempo de suspensão do consumidor, depois que o produtor terminar de executar, o consumidor sai do encadeamento antes do consumo, então o produtor deve pausar e aguardar (bloquear o encadeamento do produtor), para esperar que o encadeamento do consumidor transfira os dados acumulados processado
  • Tempo de espera do produtor > tempo de espera do consumidor, depois que o produtor é executado, o consumidor também é executado

No caso de big data real, se a taxa na qual o produtor produz dados for maior que a taxa na qual o consumidor os consome, e quando os dados produzidos se acumulam até certo ponto, o produtor deve pausar e aguardar (bloquear o produtor thread) para que aguarde o thread do consumidor terminar de processar os dados acumulados e vice-versa. Em um ambiente multi-threaded, cada um de nossos programadores deve controlar esses detalhes por conta própria, principalmente levando em consideração a eficiência e segurança de threads, e isso trará muita complexidade aos nossos programas.

Existe uma fila de bloqueio BlockingQueue em java, mas parece não haver nenhuma fila de bloqueio relacionada em Qt, precisamos controlar esses detalhes por nós mesmos

4. Qt BlockingQueue fila de bloqueio thread-safe personalizada

insira a descrição da imagem aqui

O seguinte define uma fila de bloqueio simples:

#include <QCoreApplication>
#include <QWaitCondition>
#include <QQueue>
#include <QMutex>
#include <QThread>
#include <QDebug>

template <typename T>
class BlockingQueue
{
    
    
public:
    BlockingQueue() {
    
    }
    void put(const T& value)
    {
    
    
        QMutexLocker locker(&m_mutex);
        m_queue.enqueue(value);
        m_condition.wakeOne();   //唤醒等待队列中的一个线程(来自wait)
    }
    T take()
    {
    
    
        QMutexLocker locker(&m_mutex);
        while (m_queue.isEmpty()) {
    
    
            m_condition.wait(&m_mutex);
        }
        return m_queue.dequeue();
    }
    bool isEmpty() const
    {
    
    
        QMutexLocker locker(&m_mutex);
        return m_queue.isEmpty();
    }
    int size() const
    {
    
    
        QMutexLocker locker(&m_mutex);
        return m_queue.size();
    }

private:
    QQueue<T> m_queue;
    mutable QMutex m_mutex;
    QWaitCondition m_condition;
};

Essa classe BlockingQueue usa QMutex e QWaitCondition para garantir a segurança do thread e implementa métodos como put, take, isEmpty e size. Entre eles, o método put é usado para inserir elementos na fila, o método take é usado para remover elementos da fila, o método isEmpty é usado para determinar se a fila está vazia e o método size é usado para obter o número de elementos na fila.

No método put, primeiro adquirimos o mutex, depois inserimos o elemento na fila e ativamos um thread em espera (chamamos o thread em take()) por meio do método wakeOne() de QWaitCondition. No método take, primeiro adquirimos o mutex, depois chamamos o método wait() de QWaitCondition quando a fila está vazia e esperamos até que outras threads insiram elementos na fila e ativem a thread atual.

A função de mutável é permitir que as variáveis ​​de membro m_mutex e m_notEmpty da classe BlockingQueue sejam modificadas na função de membro const. Isso ocorre porque os threads do produtor e do consumidor precisam modificar essas duas variáveis ​​de membro ao adicionar ou remover elementos da fila de bloqueio. No entanto, como os métodos take() e tryTake() são ambos funções de membro const, o compilador relatará um erro se m_mutex e m_notEmpty não forem declarados como tipos mutáveis.

♦ Uso: ·

static BlockingQueue<int> queue;

class Producer : public QThread
{
    
    
public:
    void run() override
    {
    
    
        for (int i = 0; i < 10; ++i) {
    
    
            queue.put(i);
            qDebug() << "Producer thread: " << QThread::currentThreadId() << ", value: " << i;
            msleep(500); // sleep for 0.5 second

        }
    }
};

class Consumer : public QThread
{
    
    
public:
    void run() override
    {
    
    
        int value = 0;
        while (true) {
    
    
            value = queue.take();
            qDebug() << "Consumer thread: " << QThread::currentThreadId() << ", value: " << value;
        }
    }
};

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);

    Producer producer;
    Consumer consumer1, consumer2;

    producer.start();
    consumer1.start();
    consumer2.start();

    return a.exec();
}

♦ Resultado da corrida:

insira a descrição da imagem aqui

O acima inclui um thread produtor e dois threads consumidores. O thread produtor insere 10 inteiros na fila e faz uma pausa de 0,5 segundos após cada elemento ser inserido. Os dois encadeamentos consumidores buscam continuamente elementos da fila e emitem o ID do encadeamento atual e o valor do elemento buscado. Quando a fila está vazia, a thread consumidora entra em um estado de espera até que outras threads insiram elementos na fila.

Acho que você gosta

Origin blog.csdn.net/qq_16504163/article/details/130545173
Recomendado
Clasificación