C++ Qt 多线程 QThread 与线程安全锁
Qt 多线程概述
Qt 中的多线程 API 是对系统提供的线程接口(如 Linux pthread)的封装,相比原生 API 更便于使用。
QThread
创建线程需要实例化 QThread 类并指定入口函数。通常通过继承 QThread 并重写 run() 函数来实现多态入口。
常用 API:
| 函数 | 说明 |
|---|---|
| run() | 线程的入口函数 |
| start() | 调用 run() 开始执行线程 |
| currentThread() | 返回当前执行线程的 QThread 指针 |
| isRunning() | 如果线程正在运行则返回 true |
| sleep() / msleep() / usleep() | 使线程休眠,单位为秒/毫秒/微秒 |
| wait() | 阻塞线程直到线程完成或超时 |
| terminate() | 终止线程的执行 |
| finished() | 线程结束时发出的信号 |
实操 Demo:倒计时程序
通过新线程进行计时,利用信号槽通知主线程更新界面。
- 拖拽一个 LCD 控件,初始值设置为 10。
- 新增 C++ Class 类,继承 QThread 父类。
- 重写父类的 run 方法
void run()。 - 新线程不能直接修改界面内容,需通过信号通知主线程。
- 循环 10 次,每次休眠 1s,发送信号 notify。
- 在 Widget 中连接信号槽,槽函数修改 lcd 的值。
- 构造函数中启动线程。
主要源码如下:
// thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QWidget>
#include <QThread>
class Thread : public QThread {
Q_OBJECT
public:
Thread();
void run();
signals:
void notify();
};
#endif // THREAD_H
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "thread.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
Thread thread;
private slots:
void update_lcd();
};
#endif // WIDGET_H
// thread.cpp
#include "thread.h"
Thread::Thread() {}
void Thread::run() {
for (int i = 0; i < 10; i++) {
QThread::sleep(1);
emit notify();
}
}
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
connect(&thread, &Thread::notify, this, &Widget::update_lcd);
thread.start();
}
Widget::~Widget() {
delete ui;
}
void Widget::update_lcd() {
int val = ui->lcdNumber->value();
val--;
ui->lcdNumber->display(val);
}
为什么使用多线程
客户端开发中,多线程的主要目的是提升用户体验,避免主线程被阻塞。
- IO 密集型操作:上传/下载大文件、加载资源等耗时操作应放在子线程。
- 主线程职责:负责事件循环,响应用户操作。若主线程被 IO 阻塞,窗口将无响应。
线程安全问题
多线程并发访问公共资源可能导致数据不一致,需加锁保护。
加锁 QMutex
将多个线程要访问的公共资源通过锁保护起来,将并发执行变为串行执行。
- 主要方法:lock() 加锁、unlock() 解锁。
Demo:不加锁 vs 加锁
- 创建两个线程对象,静态成员变量 num 初始为 0。
- 每个线程循环 100000 次增加 num。
- 不加锁时,结果往往小于 200000(数据竞争)。
- 加锁后,结果为 200000。
注意:锁必须是静态的,确保所有线程使用同一把锁。
QMutexLocker(锁 RAII)
使用 QMutexLocker 对象替代手动 lock/unlock,构造时传入 mutex 锁对象,析构时自动释放锁,防止忘记释放锁的情况。
条件变量与信号量
- QWaitCondition:用于等待条件满足,核心方法包括 wait(), wakeOne(), wakeAll()。
- QSemaphore:用于资源计数,核心方法包括 acquire(), release(), tryAcquire()。
两者本质与 Linux 多线程同步机制一致,用于干预线程执行顺序和保证同步。


