在多线程编程中,线程间的同步是一个核心问题。在处理线程等待时,经常会写出高CPU占用率的代码,其中最典型的就是使用忙等待(busy waiting)。本文将详细介绍如何使用Qt框

架中的QWaitCondition类来优雅地解决这一问题,有效降低CPU占用率。

一、占用率高分析

如上图,这种常见写法确实会导致 CPU 常驻一定百分比左右,原因是:

  1. 线程永远不会退出  while(true) 保持运行,即使没有任务也会醒来一次;

  2. msleep(500) 只是让线程休眠 500ms 休眠期间 CPU 占用是 0,但醒来后要判断条件、调用函数 → 系统监视器会统计为少量 CPU 占用,就算循环里几乎什么都不做,CPU 也要处理线程切换;

总结,采用msleep其是一种实时性CPU 占用 的权衡,msleep 越短线程被唤醒得越频繁,响应快,CPU 占用越高(比如 msleep(1) 就几乎是“忙等”,会吃掉比较多 CPU)。而 msleep 越长 CPU 占用低,但可能延迟处理任务(比如 500ms 意味着最坏情况延迟半秒)。这种写法的问题显而易见:

  • CPU持续进行无效的检查循环
  • 即使使用msleep,仍然会产生不必要的上下文切换
  • 响应延迟不可控
  • 资源浪费不可避免

二、QWaitCondition:优雅的解决方案

QWaitCondition是Qt提供的条件变量实现,它允许线程在特定条件满足之前进入休眠状态,从而避免忙等待。

2.1、基本工作原理

QWaitCondition的核心机制:

  1. wait() - 线程释放互斥锁并进入休眠状态
  2. wakeOne()/wakeAll() - 唤醒一个或所有等待的线程
  3. 被唤醒的线程重新获取互斥锁并继续执行

2.2、wakeOne唤醒一个

下方演示一个最小可运行的 Qt C++ QWaitCondition 生产者–消费者 Demo
这个程序有两个线程:

  • Producer:每隔一秒产生一个任务。

  • Consumer:只有收到任务才会被唤醒并处理,空闲时 CPU 完全是 0%

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QDebug>// 全局队列 & 条件变量
QMutex mutex;
QWaitCondition cond;
QQueue<int> queue;// 消费者线程
class Consumer : public QThread {
protected:void run() override {while (true) {mutex.lock();// 如果队列为空,就等待while (queue.isEmpty()) {cond.wait(&mutex); // 会自动解锁并挂起,直到被唤醒}int task = queue.dequeue();mutex.unlock();qDebug() << "Consumer: processing task" << task;QThread::msleep(200); // 模拟耗时任务}}
};// 生产者线程
class Producer : public QThread {
protected:void run() override {int counter = 0;while (true) {QThread::sleep(1); // 每秒生产一个任务mutex.lock();queue.enqueue(++counter);qDebug() << "Producer: produced task" << counter;cond.wakeOne(); // 唤醒一个等待的消费者mutex.unlock();}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Consumer consumer;Producer producer;consumer.start();producer.start();return a.exec();
}

Producer: produced task 1
Consumer: processing task 1
Producer: produced task 2
Consumer: processing task 2
...

此时观察任务管理器 / top,可以看到 CPU 占用在 没有任务时几乎 0%,有任务时才会短暂占用

2.3、wakeAll唤醒所有

下面创建 3 个消费者线程,同时等待任务队列里的数据,同时用 QMutexLocker 可以避免忘记 unlock() 导致死锁;

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QDebug>// 全局任务队列 & 条件变量
QMutex mutex;
QWaitCondition cond;
QQueue<int> queue;// 消费者线程
class Consumer : public QThread {
public:Consumer(int id) : m_id(id) {}protected:void run() override {while (true) {QMutexLocker locker(&mutex);// 如果队列为空,就等待while (queue.isEmpty()) {cond.wait(&mutex);  // 注意:这里必须传裸 QMutex 指针}int task = queue.dequeue();locker.unlock();  // 手动提前解锁,让其他线程能进入qDebug() << "Consumer" << m_id << "processing task" << task;QThread::msleep(300); // 模拟耗时任务}}private:int m_id;
};// 生产者线程
class Producer : public QThread {
protected:void run() override {int counter = 0;while (true) {QThread::sleep(1); // 每秒生产一个任务{QMutexLocker locker(&mutex);queue.enqueue(++counter);qDebug() << "Producer produced task" << counter;//cond.wakeOne(); // 唤醒一个等待的消费者cond.wakeAll();//所有消费者都被唤醒} // locker 作用域结束时自动解锁}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 启动 3 个消费者线程Consumer c1(1), c2(2), c3(3);Producer producer;c1.start();c2.start();c3.start();producer.start();return a.exec();
}
  • cond.wakeOne() → 每次只唤醒 一个等待的消费者(其他仍然挂起)。

  • cond.wakeAll() → 会唤醒 所有等待的消费者(此时谁先拿到锁,谁先消费)

QMutexLocker locker(&mutex); → 构造时自动加锁,析构时自动解锁。

cond.wait() 推荐传裸 QMutex*,不要传 QMutexLocker;

在消费者里 locker.unlock() 提前解锁,避免持锁期间去做耗时任务(否则会阻塞其他线程取任务);

cond.wait(&mutex, 3000); // 也可以设置3秒超时防止死锁

三、总结

QWaitCondition是Qt中处理线程同步的强大工具,它通过避免忙等待显著降低了CPU占用率。关键要点:

  1. 使用条件变量替代忙等待循环
  2. 结合互斥锁确保线程安全
  3. 合理使用超时机制避免无限阻塞
  4. 选择适当的唤醒策略优化性能

通过正确使用QWaitCondition,可以构建出既高效又稳定的多线程应用程序,在保证功能正确性的同时最小化系统资源消耗,高质量的多线程代码不仅要功能正确,还要在性能和资源使用上做到优雅高效,QWaitCondition正是达到这一目标的利器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94036.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94036.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94036.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pcl求平面点云的边界凸包点

基本流程1&#xff0c;读入点云&#xff0c;并去除无效点2&#xff0c;拟合平面3&#xff0c;去除离平面距离较远的点4&#xff0c;对点云进行平面投影5&#xff0c;进行convex_hull运算初学者&#xff0c;暂时不知道能用来干嘛。练手还是非常不错的&#xff01;#define _CRT_S…

Windows系统上使用GIT

首先破除一下畏惧心理&#xff1a;在Windows上使用git和在linux系统中的使用方法是一样的&#xff0c;只是安装方式没那么便捷&#xff0c;毕竟linux中安装git只需要一行命令 GIT下载地址 如果你的电脑的CPU是64位的&#xff0c;就点击&#xff1a; Git-2.50.1-64-bit.exe 如果…

《设计模式之禅》笔记摘录 - 17.模板方法模式

模板方法模式的定义模板方法模式(Template Method Pattern)是如此简单&#xff0c;以致让你感觉你已经能够掌握其精髓了。其定义如下&#xff1a;Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.Template Method lets subclasses r…

SpreadJS 协同服务器 MongoDB 数据库适配支持

为了支持 SpreadJS 协同编辑场景&#xff0c;协同服务器需要持久化存储文档、操作、快照及里程碑数据。本文介绍了 MongoDB 数据库适配器的实现方法&#xff0c;包括集合初始化、适配器接口实现以及里程碑存储支持。 一、MongoDB 集合初始化 协同编辑服务需要以下集合&#x…

Ubuntu 主机名:精通配置与管理

主机名&#xff08;hostname&#xff09;是Linux系统中用于标识网络上特定设备的名称&#xff0c;它在网络通信、服务配置&#xff08;如 Kubernetes 集群、数据库&#xff09;以及日志记录中扮演着至关重要的角色。对于初学者来说&#xff0c;配置主机名似乎很简单&#xff0c…

C/C++ 协程:Stackful 手动控制的工程必然性

&#x1f680; C/C 协程&#xff1a;Stackful 手动控制的工程必然性 引用&#xff1a; C/C 如何正确的切换协同程序&#xff1f;&#xff08;基于协程的并行架构&#xff09; #mermaid-svg-SXgplRf3WRYc8A7l {font-family:"trebuchet ms",verdana,arial,sans-serif;…

新手向:使用STM32通过RS485通信接口控制步进电机

新手向&#xff1a;使用STM32通过RS485通信接口控制步进电机 准备工作 本文使用的STM32芯片是STM32F407ZGTx&#xff0c;使用的电机是57步进电机&#xff0c;驱动器是用的是时代超群的RS485总线一体化步进电机驱动器&#xff08;42 型&#xff1a;ZD-M42P-485&#xff09;。使…

设计模式笔记_行为型_命令模式

1.命令模式介绍命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求或操作封装为对象&#xff0c;使得可以用不同的请求对客户端进行参数化。命令模式的核心思想是将方法调用、请求或操作封装到一个独立的命令对象中&#xff0c;从而使得客…

详解MySQL中的多表查询:多表查询分类讲解、七种JOIN操作的实现

精选专栏链接 &#x1f517; MySQL技术笔记专栏Redis技术笔记专栏大模型搭建专栏Python学习笔记专栏深度学习算法专栏 欢迎订阅&#xff0c;点赞&#xff0b;关注&#xff0c;每日精进1%&#xff0c;与百万开发者共攀技术珠峰 更多内容持续更新中&#xff01;希望能给大家带来…

vue3+elemeent-plus, el-tooltip的样式修改不生效

修改后的样式&#xff0c;直接贴图&#xff0c;经过删除出现悬浮1、在书写代码的时候切记effect“light”&#xff0c;如果你需要的是深色的样式:disabled"!multiple" 是否禁用<el-tooltip effect"light" placement"top" content"请先选…

网页作品惊艳亮相!这个浪浪山小妖怪网站太治愈了!

大家好呀&#xff01;今天要给大家分享一个超级治愈的网页作品——浪浪山小妖怪主题网站&#xff01;这个纯原生开发的项目不仅颜值在线&#xff0c;功能也很能打哦&#xff5e;至于灵感来源的话&#xff0c;要从一部动画说起。最近迷上了治愈系动画&#xff0c;就想做一个温暖…

搭建最新--若依分布式spring cloudv3.6.6 前后端分离项目--步骤与记录常见的坑

首先 什么拉取代码&#xff0c;安装数据库&#xff0c;安装redis&#xff0c;安装jdk这些我就不说了 导入数据库 &#xff1a;数据库是分库表的 &#xff0c;不要建错了 【一定要注意&#xff0c;不然nacos读取不到配置文件】这个是给nacos用的这个是给项目配置或项目用的2. 服…

分布式唯一 ID 生成方案

在复杂分布式系统中&#xff0c;往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中&#xff0c;数据日渐增长&#xff0c;对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息&#xff0c;数据库的自增 ID 显然不能…

飞算JavaAI赋能高吞吐服务器模拟:从0到百万级QPS的“流量洪峰”征服之旅

引言&#xff1a;当“流量洪峰”来袭&#xff0c;如何用低代码驯服高并发&#xff1f; 在数字化时代&#xff0c;从电商平台的“双11”大促到社交网络的突发热点事件&#xff0c;再到金融系统的实时交易高峰&#xff0c;服务器时刻面临着**高吞吐量&#xff08;High Throughput…

C#数据访问帮助类

一.中文注释using System; using System.Data; using System.Xml; using System.Data.SqlClient; using System.Collections;namespace Microsoft.ApplicationBlocks.Data.Ch {/// <summary>/// SqlServer数据访问帮助类/// </summary>public sealed class SqlHelp…

B站 韩顺平 笔记 (Day 21)

目录 1&#xff08;面向对象高级部分练习题&#xff09; 1.1&#xff08;题1&#xff09; 1.2&#xff08;题2&#xff09; 1.3&#xff08;题3&#xff09; Vehicles接口类&#xff1a; Horse类&#xff1a; Boat类&#xff1a; Plane类&#xff1a; VehiclesFactory…

Linux(十四)——进程管理和计划任务管理

文章目录前言一、程序与进程的关系1.1 程序与进程的定义1.2 父进程与子进程二、查看进程信息2.1 ps 命令&#xff08;重点&#xff09;2.2 动态查看进程信息top命令&#xff08;重点&#xff09;2.3 pgrep命令查询进程信息2.4 pstree命令以树形结构列出进程信息三、进程的启动方…

太阳光模拟器在无人机老化测试中的应用

在无人机技术飞速发展的当下&#xff0c;其户外作业环境复杂多变&#xff0c;长期暴露在阳光照射下&#xff0c;部件老化问题日益凸显&#xff0c;严重影响无人机的性能与寿命。紫创测控Luminbox专注于太阳光模拟器技术创新与精密光学测试系统开发&#xff0c;其涵盖的 LED、卤…

网络原理-TCP_IP

1.UDP&#xff08;即用户数据报协议&#xff09;UDP是一种无连接的传输层协议&#xff0c;提供简单的、不可靠的数据传输服务。它不保证数据包的顺序、可靠性或重复性&#xff0c;但具有低延迟和高效率的特点。UDP协议段格式16位UDP⻓度,表⽰整个数据报(UDP⾸部UDP数据)的最⼤⻓…

GitHub Actions YAML命令使用指南

version: 2 updates:- package-ecosystem: "github-actions"directory: "/"schedule:interval: "weekly"这段代码是 Dependabot 的配置文件&#xff08;通常放在 .github/dependabot.yml 中&#xff09;&#xff0c;它的作用是 自动化管理 GitHu…