Qt 中的队列解析

Qt 中的队列解析

一、Qt 中的队列解析

Qt 提供了多种机制来实现类似队列的功能,主要涉及以下几个方面:

  1. 数据结构队列:QQueue<T>

    • 本质: QQueue<T> 是一个模板类,它实际上是 QList<T> 的一个便捷别名。它提供了先进先出(FIFO)的队列操作接口。

    • 核心操作:

      • enqueue(const T &value): 将元素 value 添加到队列的尾部(入队)。
      • T dequeue(): 移除并返回队列头部的元素(出队)。如果队列为空,行为未定义(通常会导致崩溃),使用前最好检查 isEmpty()
      • T &head(): 返回队列头部元素的引用(不移除)。
      • const T &head() const: 返回队列头部元素的常量引用(不移除)。
      • T &first(): 同 head()
      • const T &first() const: 同 head() const
      • T &last(): 返回队列尾部元素的引用。
      • const T &last() const: 返回队列尾部元素的常量引用。
      • bool isEmpty() const: 检查队列是否为空。
      • int size() const: 返回队列中元素的数量。
    • 示例:

      cpp 复制代码
      #include <QQueue>
      #include <QDebug>
      
      int main() {
          QQueue<int> queue;
      
          // 入队
          queue.enqueue(10);
          queue.enqueue(20);
          queue.enqueue(30);
      
          // 检查头部
          qDebug() << "Head:" << queue.head(); // 输出 10
      
          // 出队
          int firstOut = queue.dequeue(); // firstOut = 10
          qDebug() << "Dequeued:" << firstOut;
      
          // 再次检查头部
          qDebug() << "Head now:" << queue.head(); // 输出 20
      
          return 0;
      }
    • 注意: QQueue 本身不是线程安全 的。如果需要在多线程环境中使用队列,必须使用互斥锁(如 QMutexQReadWriteLock)进行保护。

  2. 跨线程通信队列:信号与槽的队列连接 (Qt::QueuedConnection)

    • 概念: 这是 Qt 事件驱动和线程模型的核心机制之一。当信号通过队列连接 (Qt::QueuedConnection) 方式连接到槽时,信号的发射并不会立即调用槽函数。

    • 工作原理:

      1. 信号发射时,一个包含信号参数副本的事件(QMetaCallEvent)被放入接收者对象所在线程的事件队列中。
      2. 接收者线程的事件循环 (QEventLoop) 在后续的某个时刻(当它处理事件队列时)会取出这个事件。
      3. 事件循环根据事件中的信息调用对应的槽函数。
    • 特点:

      • 异步执行: 槽函数在接收者线程中异步执行,与信号发射线程解耦。这保证了线程安全。
      • 跨线程通信: 这是不同线程间对象通信的标准和安全方式。
      • 事件驱动: 依赖于接收者线程正在运行的事件循环。
    • 连接方式:

      cpp 复制代码
      QObject::connect(sender, &Sender::signalName,
                       receiver, &Receiver::slotName,
                       Qt::QueuedConnection); // 显式指定队列连接
      • senderreceiver 位于不同线程,且没有显式指定连接类型时,默认的连接类型 Qt::AutoConnection 会被解释为 Qt::QueuedConnection
    • 示例场景: 工作线程完成计算后,通过信号将结果发送到主线程的某个对象进行处理(如更新 UI)。

  3. 任务队列:QThreadPoolQRunnable

    • 概念: QThreadPool 管理了一个线程池,用于执行可运行的任务 (QRunnable)。你可以将多个 QRunnable 对象提交给线程池,它们会被放入一个内部队列中。

    • 工作原理:

      1. 用户创建继承自 QRunnable 的子类,并重写 run() 方法定义任务逻辑。
      2. 创建该任务对象。
      3. 调用 QThreadPool::globalInstance()->start(runnable) 将任务提交给全局线程池。
      4. 线程池中的空闲线程会从队列中取出任务并执行其 run() 方法。
      5. 如果所有线程都忙,新提交的任务会排队等待,直到有线程空闲。
    • 特点:

      • 任务队列: 线程池内部维护了一个待执行任务的队列。
      • 线程复用: 避免了频繁创建和销毁线程的开销。
      • 并行执行: 允许多个任务在多个线程上并行执行(取决于线程池大小)。
      • 管理: 可以设置全局线程池或创建私有线程池,并设置最大线程数等。
    • 示例:

      cpp 复制代码
      #include <QRunnable>
      #include <QThreadPool>
      #include <QDebug>
      
      class MyTask : public QRunnable {
      public:
          void run() override {
              qDebug() << "Task running in thread:" << QThread::currentThread();
              // 执行耗时操作...
          }
      };
      
      int main() {
          MyTask *task1 = new MyTask();
          MyTask *task2 = new MyTask();
      
          // 提交任务到全局线程池队列
          QThreadPool::globalInstance()->start(task1);
          QThreadPool::globalInstance()->start(task2);
      
          // 主线程可以继续执行其他操作...
          return 0;
      }

总结:

Qt 中的"队列"概念体现在几个层面:

  1. QQueue<T> 作为数据结构,提供 FIFO 操作。适用于单线程或受保护的多线程环境。
  2. 信号与槽的 Qt::QueuedConnection 作为跨线程通信机制 的核心。信号发射被转换为事件放入接收线程的事件队列,由事件循环异步处理。这是实现线程间安全通信的标准方式。
  3. QThreadPool 管理一个任务队列 。用户提交的 QRunnable 任务被排队,由线程池中的工作线程取出执行,实现任务的并行处理。

二、代码示例

cpp 复制代码
#include <QCoreApplication>
#include <QQueue>
#include <QMutex>
#include <QMutexLocker>
#include <QThread>
#include <QDebug>

// 全局队列 + 互斥锁(多线程必须加锁)
QQueue<int> g_queue;
QMutex       g_mutex;

// 生产者线程:往队列里放数据
class Producer : public QThread
{
protected:
    void run() override
    {
        for (int i = 1; i <= 10; ++i)
        {
            // 自动加锁/解锁
            QMutexLocker locker(&g_mutex);

            g_queue.enqueue(i);
            qDebug() << "生产:" << i << " 队列大小:" << g_queue.size();

            // 模拟耗时
            msleep(200);
        }
    }
};

// 消费者线程:从队列取数据
class Consumer : public QThread
{
protected:
    void run() override
    {
        while (true)
        {
            QMutexLocker locker(&g_mutex);

            if (!g_queue.isEmpty())
            {
                int val = g_queue.dequeue();
                qDebug() << "消费:" << val << " 队列大小:" << g_queue.size();
            }
            else
            {
                // 队列为空,稍等再取
                qDebug() << "队列为空,等待...";
            }

            msleep(300);
        }
    }
};

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

    Producer p;
    Consumer c;

    p.start();
    c.start();

    p.wait();
    c.wait();

    return a.exec();
}

运行结果:

cpp 复制代码
08:57:14: Starting G:\Learning\Qt\QJsonSchema\build\Desktop_Qt_6_9_0_MinGW_64_bit-Debug\debug\QJsonSchema.exe...
生产: 1  队列大小: 1
生产: 2  队列大小: 2
生产: 3  队列大小: 3
生产: 4  队列大小: 4
生产: 5  队列大小: 5
生产: 6  队列大小: 6
生产: 7  队列大小: 7
生产: 8  队列大小: 8
生产: 9  队列大小: 9
生产: 10  队列大小: 10
消费: 1  队列大小: 9
消费: 2  队列大小: 8
消费: 3  队列大小: 7
消费: 4  队列大小: 6
消费: 5  队列大小: 5
消费: 6  队列大小: 4
消费: 7  队列大小: 3
消费: 8  队列大小: 2
消费: 9  队列大小: 1
消费: 10  队列大小: 0
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...
队列为空,等待...


相关推荐
原来是猿7 小时前
QT初识【创建项目+对象树】
开发语言·qt
-凌凌漆-8 小时前
【Qt】 QSerialPort::flush()介绍
开发语言·qt
咸鱼翻身小阿橙10 小时前
QT P4
数据库·qt·nginx
Wild_Pointer.12 小时前
项目实战:编写CMakeLists管理Qt+OpenCV项目
开发语言·c++·qt
星越华夏13 小时前
Qt5状态栏刷新显示内容
python·qt
sycmancia13 小时前
Qt——Qt中的文件操作、文本流和数据流
开发语言·qt
雾岛听蓝1 天前
Qt操作指南:窗口组成与菜单栏
开发语言·经验分享·笔记·qt
(Charon)1 天前
【C++/Qt】C++/Qt 实现 TCP Server:支持启动监听、消息收发、日志保存
c++·qt·tcp/ip
咸鱼翻身小阿橙1 天前
QT-P3
开发语言·qt·计算机视觉