多线程
- QThread 类简介
- 使用线程
- 线程同步
- 互斥锁
QThread 类简介
一个 QThread 类的对象管理一个线程。在设计多线程程序的时候,需要从 QThread 继承定义线程类,并重新定义 QThread 的虚函数 run(),在函数 run() 里处理线程的事件循环。
应用程序的线程称为主线程,创建的其他线程称为工作线程。一般会在主线程里创建工作线程,并用函数 start() 开始执行工作线程的任务。函数 start() 会在其内部调用函数 run() 进入工作线程的事件循环,函数 run() 的程序体一般是一个无限循环,可以在函数 run() 里调用函数 exit() 或 quit() 结束线程的事件循环,或在主线程里调用函数 terminate() 强制结束线程。
QThread 常用 API
函数 | 功能 |
---|---|
run() | 线程的入口函数 |
start() | 通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做 |
cuttentThread() | 返回一个指向管理当前执行线程的QThread的指针 |
isRunning() | 如果线程正在运行则返回 true,否则返回 false |
sleep() / msleep() / usleep() | 使线程休眠,单位为秒 / 毫秒 / 微秒 |
wait() | 阻塞线程,直到满足以下任何一个条件: 与此 QThread 对象关联的线程已经完成执行(即当它从run() 返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了几毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(现成必须从 run() 返回)。如果等待超时,此函数将返回false。 |
terminate() | 终止线程的执行。线程可以立即终止,也可以不立即终止,取决于操作系统的调度策略。在 terminate() 之后使用 QThread::wait() 来确保。 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。 |
使用线程
创建线程的步骤:
- 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数就是重写父类中的 run() 函数。
- 线程处理函数里面写入需要执行的复杂数据处理。
- 启动线程不能直接调用 run() 函数,需要使用对象来调用 start() 函数实现线程启动。
- 线程处理函数执行结束后可以定义一个信号来告诉主线程。
- 最后关闭线程。
示例:
创建 Qt 项目,UI界面如下:
再通过 New File 对话框新建一个 C++ 类,继承于 QThread 类:
创建结束后会生成文件 mythread.h 和 mythread.cpp。其中 mythread.h:
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QWidget>class MyThread : public QThread
{Q_OBJECT
public:explicit MyThread(QObject *parent = nullptr);void run();//线程任务函数signals://声明信号函数void sendTime(QString Time);
};#endif // MYTHREAD_H
mythread.cpp:
#include "mythread.h"
#include <QTime>
#include <QDebug>MyThread::MyThread(QObject *parent): QThread{parent}
{}void MyThread::run()
{while(1){QString time = QTime::currentTime().toString("hh:mm:ss");qDebug() << time;//发送信号emit sendTime(time);sleep(1);}
}
widget.h:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <mythread.h>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();void showTime(QString Time);private:Ui::Widget *ui;MyThread myThread;
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(&myThread, &MyThread::sendTime, this, &Widget::showTime);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{myThread.start();
}void Widget::showTime(QString Time)
{ui->label->setText(Time);
}
运行结果:
线程同步
在多线程程序中,由于存在多个线程,线程之间可能需要访问同一个变量,或一个线程需要等待另一个线程完成某个操作后才产生相应的动作。在 Qt 中实现线程互斥和同步常用的类有:
互斥锁:QMutex、QMutexLocker
条件变量:QWaitCondition
信号量:QSemaphore
读写锁:QReadLocker、QWriteLocker、QReadWriteLock
互斥锁
互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在 Qt 中,互斥锁主要是通过 QMutex 类来处理。
QMutex: QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途: 在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
下面有两个线程,每个线程对 num 进行五万次的加加操作,结果应该是十万:
但结果显然不是十万,这是因为一个线程对 num 加加后,又被另一个线程写入(num 加加非原子操作,有多步),这就导致前一个线程没有正确加加。这就是多个线程访问同一个变量导致的后果,此时我们通过 QMutex 类对访问变量的操作加锁即可得到正确结果:
注意:
这里的 mutex 相当于一把钥匙,如果两个线程要访问同一个共享资源,例如本例的 num,就需要通过 lock() 拿到这把钥匙,然后才可以访问该共享资源,在完成加加操作时,别的线程无法访问 num,就可以正确进行加加,访问完之后还要通过 unlock() 还回钥匙,别的线程才有机会拿到钥匙(才可以访问共享资源)。
QMutexLocker: QMutexLocker 是 QMutex 的辅助类,使用 RAII(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致死锁等问题。