当前位置: 首页 > web >正文

Qt5线程相关事项

在Qt中,线程同步和互斥锁的使用是非常重要的,尤其是在多线程操作共享资源(如FIFO队列)时。以下通俗地解释一下它们的作用:

一.  为什么需要线程同步

        想象一下,你和你的朋友一起在厨房里做蛋糕。厨房里只有一个烤箱,你们都需要用它来烤蛋糕。如果你们不协调好使用烤箱的时间,就会出现混乱的情况,比如一个人刚把蛋糕放进去,另一个人就迫不及待地打开烤箱,把蛋糕拿出来,或者两个人同时去抢烤箱,结果蛋糕都烤不好。

        在Qt的多线程编程中,线程同步的作用就像是在厨房里协调使用烤箱一样。当多个线程需要访问共享资源(比如一个FIFO队列)时,如果没有同步机制,就会出现类似抢烤箱的情况,导致数据混乱、程序出错。线程同步的目的是让线程们有序地访问共享资源,避免冲突和混乱。

二. 为什么需要互斥锁

        再举个例子,你和你的朋友一起在图书馆里看书,图书馆里只有一本珍贵的古籍,你们都需要查阅它。如果你们不互相协调,可能会出现两个人同时伸手去拿书,结果把书弄坏,或者一个人刚翻到重要的一页,另一个人就抢过去,导致谁也看不成。

        在Qt编程中,互斥锁的作用就像是在图书馆里协调使用那本珍贵的古籍。当多个线程需要访问同一个FIFO队列时,互斥锁可以确保在某一时刻,只有一个线程能够操作队列。具体来说:

  • 写操作:当一个线程往FIFO队列里写数据时,互斥锁会“锁住”队列,让其他线程不能同时写入。这样可以避免数据写入时出现冲突,比如两个线程同时往队列里插入数据,结果数据顺序混乱。

  • 读操作:同样,当一个线程从FIFO队列里读取数据时,互斥锁也会“锁住”队列,防止其他线程同时读取或写入。这样可以保证读取的数据是完整且正确的,避免出现读到一半数据被另一个线程修改的情况。

        线程同步和互斥锁在Qt中非常重要,它们的作用就像是在多线程环境中协调使用共享资源的“交通规则”。通过合理使用线程同步和互斥锁,可以避免多线程操作共享资源时出现的冲突和错误,确保程序的正确性和稳定性。

互斥锁的工作原理
  • 加锁(mutex.lock():当一个线程调用mutex.lock()时,它会检查锁的状态。如果锁是空闲的,线程会获取锁并继续执行。如果锁已经被其他线程占用,当前线程会被阻塞,直到锁被释放。

  • 解锁(mutex.unlock():当一个线程完成对共享资源的操作后,它会调用mutex.unlock()释放锁。此时,其他被阻塞的线程会尝试获取锁并继续执行

例如:

  • 生产者线程在mutex.lock()处加锁,开始写入数据。

  • 消费者线程在mutex.lock()处被阻塞,等待生产者线程解锁。

  • 生产者线程完成写入后调用mutex.unlock(),消费者线程获取锁并开始读取数据。

三.线程退出

        我们可以将生产者-消费者模型应用到网络数据处理的场景中。具体来说,网络接收线程作为生产者,将接收到的数据放入一个FIFO队列中;数据处理线程作为消费者,从FIFO队列中取出数据进行处理。为了确保线程安全,我们需要在访问FIFO队列时使用互斥锁。

  1. 网络接收线程(生产者)

    • 监听TCP连接,接收数据。

    • 将接收到的数据放入FIFO队列中,操作队列时使用互斥锁。

  2. 数据处理线程(消费者)

    • 从FIFO队列中取出数据进行处理,操作队列时使用互斥锁。

    • 处理后的数据可以发送到主线程进行显示。

  3. FIFO队列

    • 使用QQueue作为FIFO队列。

    • 使用QMutex保护队列的访问,确保线程安全。

#include <QCoreApplication>
#include <QThread>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QQueue>
#include <QMutex>
#include <QTimer>// FIFO队列和互斥锁
QQueue<QByteArray> fifoQueue;
QMutex fifoMutex;class NetworkReceiver : public QObject
{Q_OBJECTpublic:NetworkReceiver() : server(new QTcpServer(this)) {}~NetworkReceiver() {}public slots:void startListening(int port){if (server->listen(QHostAddress::Any, port)){qDebug() << "Listening on port" << port;connect(server, &QTcpServer::newConnection, this, &NetworkReceiver::handleNewConnection);}else{qDebug() << "Failed to listen on port" << port;}}private slots:void handleNewConnection(){QTcpSocket *socket = server->nextPendingConnection();connect(socket, &QTcpSocket::readyRead, this, [this, socket]() {QByteArray data = socket->readAll();qDebug() << "Received data:" << data;fifoMutex.lock(); // 加锁fifoQueue.enqueue(data); // 将数据放入队列fifoMutex.unlock(); // 解锁});}private:QTcpServer *server;
};class DataProcessor : public QObject
{Q_OBJECTpublic:DataProcessor() {}~DataProcessor() {}public slots:void processData(){while (!QThread::currentThread()->isInterruptionRequested()){fifoMutex.lock(); // 加锁if (!fifoQueue.isEmpty()){QByteArray data = fifoQueue.dequeue(); // 从队列中取出数据fifoMutex.unlock(); // 解锁QByteArray processedData;for (char c : data){processedData.append(c + 1); // 每个字节加1}qDebug() << "Processed data:" << processedData;emit dataProcessed(processedData); // 发出处理后的数据}else{fifoMutex.unlock(); // 解锁QThread::currentThread()->msleep(100); // 等待新数据}}qDebug() << "Data processor thread is exiting safely.";}signals:void dataProcessed(const QByteArray &data);
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QThread receiverThread;QThread processorThread;NetworkReceiver receiver;DataProcessor processor;receiver.moveToThread(&receiverThread);processor.moveToThread(&processorThread);QObject::connect(&receiverThread, &QThread::started, &receiver, &NetworkReceiver::startListening);QObject::connect(&processor, &DataProcessor::dataProcessed, [](const QByteArray &data) {qDebug() << "Displaying data:" << data; // 在主线程中显示数据});receiverThread.start();processorThread.start();receiver.startListening(12345); // 监听端口12345int ret = a.exec();processorThread.requestInterruption();receiverThread.quit();receiverThread.wait();processorThread.quit();processorThread.wait();return ret;
}

1. run()函数

  • 启动线程:当你调用producer.start()时,Qt会启动一个新的线程,并在新线程中调用Producer类的run()方法。
  • 执行run()run()方法会按照代码逻辑执行,直到执行完毕。
  • 线程结束:当run()方法执行完毕后,线程会自动结束。
  • 等待结束:调用producer.wait()时,主线程会阻塞,直到生产者线程的run()方法执行完毕并退出。
  • run()方法是一个普通的C++函数,它会按照代码逻辑执行,直到遇到return语句或函数结束。在你的例子中,生产者线程的run()方法包含一个for循环,循环结束后,run()方法会自然结束,线程也会随之结束。
  •  在Qt中,通常会将工作逻辑放在一个单独的类中,然后将这个类的实例移动到一个新线程中。这样可以避免直接继承QThread并重写run()方法,而是将线程管理与工作逻辑分离,使代码更加清晰和模块化。

2.terminate()函数

避免使用QThread::terminate(),会立即终止线程,但不会等待线程完成清理工作,这可能导致资源泄漏或程序崩溃.

  • 通过一个volatilestd::atomic<bool>类型的标志位来控制线程的退出。线程定期检查这个标志位,如果标志位被设置为true,线程就安全退出。

  • QThread::isInterruptionRequested() 是一个更优雅且Qt风格的方式来检查线程是否应该中断。这是Qt提供的一个内置机制,用于线程安全退出。使用这个方法可以避免手动管理标志位,使代码更加简洁和安全。

  • QThread::isInterruptionRequested():这个方法用于检查是否已经向线程发送了中断请求。线程可以在其执行过程中定期调用这个方法,以决定是否应该安全退出。

  • QThread::requestInterruption():这个方法用于向线程发送中断请求。它不会立即终止线程,而是设置一个内部标志位,线程可以通过isInterruptionRequested()来检查这个标志位。

  • 线程安全退出:线程应该在合适的地方检查isInterruptionRequested(),并在检测到中断请求时执行清理工作并退出。

3.quit()函数

QThread::quit()请求线程退出

如果线程使用了事件循环(exec()),则可以通过quit()来退出事件循环。

4.wait()函数

QThread::wait()等待线程完全停止

阻塞当前线程,直到目标线程完全停止。

5.finished信号

检查线程是否已经完成。可以用来判断线程是否已经安全退出

QThreadfinished信号是Qt框架提供的信号,当线程的run()方法执行完毕或线程被终止时,会自动发出。例如:

workerThread.start(); // 启动线程
workerThread.requestInterruption(); // 请求中断
workerThread.quit(); // 请求线程退出
workerThread.wait(); // 等待线程完全停止

当线程完全停止后,QThread::finished()信号会被发出。

QThreadfinished信号是Qt框架提供的信号,用于通知线程已经完成。它在以下情况下被发出:

  • 线程的run()方法执行完毕。

  • 调用了QThread::quit()方法。

  • 调用了QThread::terminate()方法(不推荐)。

QThread::wait()方法会阻塞调用线程,直到目标线程完全停止。finished信号通常在wait()方法返回之前被发出,但具体时机取决于线程的执行情况。

6.deleLater信号

 deleteLater是Qt框架提供的槽函数,用于在对象的事件循环中安全地删除对象,用于确保对象在当前事件循环结束后被删除,。它的工作机制如下:

  • deleteLater被调用时,对象不会立即被删除。

  • 对象会被标记为待删除。

  • 在当前事件循环的下一次迭代中,对象会被删除并释放其占用的堆空间。

http://www.xdnf.cn/news/16044.html

相关文章:

  • C# 转换(is和as运算符)
  • vue-pinia
  • WebkitSpeechRecognition 语音识别
  • QT6 源,七章对话框与多窗体(5) 文件对话框 QFileDialog 篇二:源码带注释
  • nginx + uwsgi + systemd 部署 flask
  • 在Windows Server 2012 R2中安装与配置IIS服务并部署mssql靶机教程
  • springboot实战篇1
  • 基于 HAProxy 搭建 EMQ X 集群
  • C++的“链”珠妙笔:list的编程艺术
  • 解决vscode中vue格式化后缩进太小的问题,并去除分号 - 设置Vetur tabSize从2到4,设置prettier取消分号semi
  • 计算机发展史:人工智能时代的智能变革与无限可能
  • 基于WebSocket的安卓眼镜视频流GPU硬解码与OpenCV目标追踪系统实现
  • 【PTA数据结构 | C语言版】哥尼斯堡的“七桥问题”
  • C# Lambdab表达式 Var 类
  • Elupload实现多个文件上传与已上传列表中做对比,若重复则只保留已上传列表中的数据,同时告诉用户,有哪些文件重复上传了
  • 搭建种草商城框架指南
  • 飞算科技:以原创技术为翼,赋能产业数字化转型
  • Linux第三课:需要自己安装的远程登录工具PuTTY的介绍
  • 【PTA数据结构 | C语言版】求单源最短路的Dijkstra算法
  • Taro 本地存储 API 详解与实用指南
  • G7打卡——Semi-Supervised GAN
  • EMBMS1820芯祥科技18单元电池监控器芯片数据手册
  • 华控的科技布局——全球化战略与合作生态
  • 力扣(LeetCode)第 161 场双周赛
  • macbookpro m1 max本儿上速搭一个elasticsearch+kibana环境
  • 基于deepseek的LORA微调
  • 【设计模式C#】简单工厂模式(用于简化获取对象实例化的复杂性)
  • 个人中心产品设计指南:从信息展示到用户体验的细节把控
  • mongodb源代码分析createCollection命令由create.idl变成create_gen.cpp过程
  • 在.NET Core API 微服务中使用 gRPC:从通信模式到场景选型