一、Qt多线程入门
Qt中多线程的头文件引用为:
#include (或 #include “qthread.h”)
QThread 公有继承于 QObject 类
- 继承QThread 重写run方法
class MyThread:public QThread
{
......
protected:
void run()
{
........
}
}
在MainWindows.cpp中添加以下代码
//在主线程中创建子线程对象,new 一个就可以了
MyThread * subThread = new MyThread;
//启动子线程,调用 start () 方法
subThread->start();
在该示例中,线程将在 run 函数返回后退出。除非调用 exec(),否则线程中不会运行任何事件循环。
启动线程、关闭线程、阻塞线程、线程状态判断、设置优先级等使用时均是在MainWIndows中使用subThread->后加函数调用即可。
- 继承QObject类,使用moveToThread方法
通过使用 QObject::moveToThread() 将一个QObject对象移动到另外一个线程中执行。调用该函数后,这个QObject对象就会将在单独的线程中执行。但是,您可以自由地将这个QObject对象 的插槽连接到任何线程中来自任何对象的任何信号。
跨不同线程连接信号和插槽是安全的,这要归功于一种称为排队连接的机制。
Constant | 描述 |
---|---|
Qt::AutoConnection | (默认)如果信号是从与接收对象不同的线程发出的,则信号将排队,行为为 Qt::QueuedConnection。否则,将直接调用插槽,行为为 Qt::DirectConnection。连接类型在发出信号时确定。 |
Qt::DirectConnection | 当发出信号时,会立即调用该插槽。 |
Qt::QueuedConnection | 当控制返回到接收器线程的事件循环时,将调用该槽。插槽在接收器的线程中执行。 |
Qt::BlockingQueuedConnection | 与 QueuedConnection 相同,但当前线程会阻塞,直到插槽返回。此连接类型应仅在发射器和接收器位于不同线程中时使用。 |
注意: 违反此规则可能会导致应用程序死锁。
使用moveToThread方法举例:
#include <QCoreApplication>
#include <QObject>
#include <QThread>
#include <QDebug>
class Worker : public QObject
{
Q_OBJECT
public:
Worker() {}
public slots:
void doWork() {
qDebug() << "Worker thread: " << QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread* thread = new QThread(); // 创建一个线程
Worker* worker = new Worker(); // 创建一个Worker对象
worker->moveToThread(thread); // 将Worker对象移动到新线程中
//线程结束时清理线程内存
connect(thread, &QThread::finished, qthread, &QThread::deleteLater);
QObject::connect(thread, &QThread::started, worker, &Worker::doWork); // 连接信号和槽函数
thread->start(); // 启动新线程
return a.exec(); // 运行主线程
}
#include "main.moc"
在上面的代码中,我们先创建了一个新线程,然后创建了一个Worker对象。接着,我们调用了moveToThread函数,将这个对象移动到新线程中。然后,我们使用connect函数连接了新线程的started信号和Worker对象的doWork槽函数。最后,我们启动了新线程,让doWork函数在新线程中执行。
需要注意的是,在这个例子中,我们重载了Worker类,并添加了一个doWork槽函数,该函数用于输出当前线程的ID。因此,当程序运行时,我们可以看到输出的是新线程的ID,而不是主线程的ID。这表明,doWork函数确实是在新线程中执行的。
该例子来源网址:https://zhuanlan.zhihu.com/p/613390177
注意事项:
1、QThread 实例位于实例化它的旧线程中,而不是调用 run() 的新线程中。这意味着 QThread
的所有排队插槽都将在旧线程中执行。 因此,希望在新线程中调用槽的开发人员必须使用 worker-object
方法;不应将新插槽直接实现到子类 QThread 中。2、在对 QThread 进行子类化时,请记住,构造函数在旧线程中执行,而 run() 在新线程中执行。如果从两个函数访问成员变量,则从两个不同的线程访问该变量。检查这样做是否安全。
3、moveToThread() 函数的作用是将槽函数在指定的线程中被调用。也就是说worker任务类对象在主线程中。除了绑定在connect上的槽函数(及槽函数体调用的函数)外,worker的其余函数也在主线程中执行。
二、Qt管理多线程功能
2.1 启动线程
void start(Priority = InheritPriority); 槽函数
通过调用start()方法来启动线程,该方法会调用run()函数(可以看到QThread中run()为虚函数, 需要我们来重载)。run()函数可调用exec()让该线程进入事件循环。
Priority为线程优先级(下面会讲)。
2.2 关闭线程
void exit(int retcode = 0); 使线程退出事件循环, 如果该线程没有事件循环, 不做任何操作。
retcode默认为0, 表示正常返回。而非0值表示异常退出。
void quit(); 相当于exit(0) 槽函数
void terminate(); 由操作系统强行终止该线程, 可能会导致无法完成一些清理工作, 非必要情况下不使用。 槽函数
void requestInterruption();Qt5的新接口,
用于请求线程进行中断。
- bool isInterruptionRequested(); 返回true/false,
用于判断是否有终止线程的请求。
2.3 阻塞线程
bool wait(unsigned long time = ULONG_MAX); 阻塞线程time毫秒, 默认永久阻塞;
只有当线程结束(从run函数返回), 或阻塞超时才会返回; 线程结束或还未启动, wait返回值为true, 超时的返回值为false。
static void sleep(unsigned long); 阻塞xx秒, 无返回值。 static void msleep(unsigned long); 阻塞xx毫秒, 无返回值。
static void usleep(unsigned long); 阻塞xx微秒, 无返回值。
2.4线程状态判断
bool isFinished() const; 如果线程结束返回true, 否则返回false。
bool isRunning() const; 如果线程正在运行返回true, 否则返回false。
bool isInterruptionRequested() const; 如果有终止线程的请求返回true, 否则返回false;
请求可由requestInterruption()发出。
2.5 设置优先级
void setPriority(Priority priority);
用于设置正在运行的线程的优先级, 如果线程未运行,
则该返回不会执行任何操作并立刻返回。
可用start(priority)启动带优先级的线程。
指定的优先级是否生效取决于操作系统的调度, 如果是不支持线程优先级的系统上, 优先级的设置将被忽略。
优先级可以设置为QThread::Priority内除InheritPriortyd的任何值,因为InheritPriortyd是默认优先级
QThread::Priority枚举元素 | 描述 |
---|---|
QThread::IdlePriority | 没有其它线程运行时才调度 |
QThread::LowestPriority | 比LowPriority调度频率低 |
QThread::LowPriority | 比NormalPriority调度频率低 |
QThread::NormalPriority | 操作系统的默认优先级 |
QThread::HighPriority | 比NormalPriority调度频繁 |
QThread::HighestPriority | 比HighPriority调度频繁 |
QThread::TimeCriticalPriority | 尽可能频繁的调度 |
QThread::InheritPriority | 使用和创建线程同样的优先级(这是默认值) |
优先级使用规律:
1、多个线程设置了不同的优先级时所有线程与优先级最高的保持一致。
2、当只有一个线程设置优先级时,所有线程与它保持一致。
来自Qt线程优先级
2.6 信号
void started(QPrivateSignal); 在线程start后, 执行run前发出该信号。
void finished(QPrivateSignal); 在线程结束, 完全退出前发送此信号。
void terminated(); 当线程终止时,会发出此信号。
三、同步线程
跨不同线程与对象交互时要格外注意!
QMutex、QReadWriteLock、QSemaphore 和 QWaitCondition
类提供了同步线程的方法。虽然线程的主要思想是它们应该尽可能地并发,但在某些时候,线程必须停止并等待其他线程。例如,如果两个线程尝试同时访问同一个全局变量,则结果通常是未定义的。
QMutex 提供互斥锁或互斥锁。最多一个线程可以随时保存互斥锁。如果线程在互斥锁已锁定的情况下尝试获取互斥锁,则该线程将处于休眠状态,直到当前持有互斥锁的线程将其解锁。互斥锁通常用于保护对共享数据(即可同时从多个线程访问的数据)的访问。在下面的重入和线程安全部分中,我们将使用它来使类线程安全。
QReadWriteLock 类似于 QMutex,不同之处在于它区分了对共享数据的“读取”和“写入”访问,并允许多个读取器同时访问数据。在可能的情况下,使用
QReadWriteLock 而不是 QMutex 可以使多线程程序更具并发性。
QSemaphore 是 QMutex的泛化,可保护一定数量的相同资源。相比之下,互斥锁只保护一种资源。信号量示例显示了信号量的典型应用:同步对生产者和使用者之间循环缓冲区的访问。
QWaitCondition允许一个线程在满足某些条件时唤醒其他线程。一个或多个线程可以阻止等待 QWaitCondition 使用 wakeOne() 或 wakeAll() 设置条件。使用 wakeOne() 唤醒一个随机选择的事件,或使用 wakeAll() 唤醒所有事件。等待条件示例显示了如何使用 QWaitCondition 而不是 QSemaphore 解决生产者-消费者问题。
请注意,Qt的同步类依赖于使用正确对齐的指针。例如,不能将打包类与 MSVC 一起使用。