Qt多线程

一、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 一起使用。

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一个跨平台的应用程序框架,提供了丰富的多线程编程支持。Qt多线程编程主要依靠QThread类和信号槽机制来实现。 QThread类封装了线程的基本操作,使得我们可以通过继承这个类来实现自己的线程。通过重写QThread类中的run()方法,我们可以在这个方法中实现具体的线程操作。 例如,下面是一个简单的QThread子类的定义: ```c++ class MyThread : public QThread { Q_OBJECT public: explicit MyThread(QObject *parent = nullptr); protected: void run() override; signals: void resultReady(int result); }; ``` 在这个例子中,我们重写了run()方法来实现线程的具体操作。在这个方法中,我们可以调用其他Qt类或者自己实现的函数来完成多线程的任务。另外,我们还定义了一个resultReady信号,用于在线程执行完毕后向主线程发送消息。 接下来,我们可以在主线程中创建一个MyThread对象,并连接它的resultReady信号到一个槽函数中,以便在线程执行完毕后处理结果。例如: ```c++ MyThread *thread = new MyThread(this); connect(thread, &MyThread::resultReady, this, &MyClass::handleResult); thread->start(); ``` 在这个例子中,我们创建一个MyThread对象并启动它。在线程执行完毕后,它会发送resultReady信号,我们将这个信号连接到handleResult槽函数中来处理结果。 除了QThread类外,Qt还提供了许多其他的多线程编程工具,如QThreadPool类、QMutex类、QWaitCondition类等,可以帮助我们更方便地实现多线程编程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值