QT学习(4):线程管理类QThread

QT官方文档

QThread 对象管理程序中的一个控制线程。QThreads 在run()中开始执行。默认情况下,run()通过调用exec()来启动事件循环,并在线程内运行 Qt 事件循环。

1、使用moveToThread()

可以通过使用moveToThread()将工作器对象移动到线程来使用它们。

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

然后,Worker 槽函数中的代码将在单独的线程中执行。但是,可以自由地将 Worker 的槽函数连接到任何线程中来自任何对象的任何信号。跨不同线程连接信号和槽函数是安全的,这要归功于一种称为队列连接的机制。

2、子类化 QThread

使代码在单独的线程中运行的另一种方法是子类化 QThread 并重新实现run()

class WorkerThread : public QThread
{
    Q_OBJECT
    void run() override {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }
signals:
    void resultReady(const QString &s);
};

void MyObject::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}

在该示例中,线程将在 run 函数返回后退出。除非调用exec(),否则线程中不会运行任何事件循环。

重要的是要记住,QThread 实例所在线程是实例化它的旧线程,而不是调用run()的新线程。这意味着所有 QThread 的队列槽函数或invoked的方法都将在旧线程中执行。因此,希望在新线程中调用槽的开发人员必须使用worker-object方法,新槽函数不应直接定义在 QThread子类对象中。

与队列槽函数或invoked的方法不同,直接在 QThread 对象上调用的方法将在调用该方法的线程中执行。在对 QThread 进行子类化时,请记住,构造函数在旧线程中执行,而run()在新线程中执行。如果从两个函数访问成员变量,则从两个不同的线程访问该变量。需要保证线程安全。

3、管理线程

当线程为started()和finished()时,QThread 会通过信号通知,或者可以使用isFinished()和isRunning()来查询线程的状态。

您可以通过调用exit()或quit()来停止线程。在极端情况下,您可能希望使用terminate()强制终止一个执行线程。然而,这样做是危险和不鼓励的。

从 Qt 4.8开始,可以通过将finished()信号连接到QObject::deleteLater()来释放存在于刚刚结束的线程中的对象。

使用wait()阻塞调用线程,直到另一个线程完成执行(或直到经过指定的时间)。

QThread 还提供静态的、独立于平台的休眠功能:sleep(),msleep(),usleep()分别允许全秒、毫秒和微秒。

注意:wait()和sleep()函数通常应该是不必要的,因为 Qt 是一个事件驱动的框架。请考虑监听finished()信号,而不是wait()。请考虑使用QTimer函数,而不是sleep()函数。

静态函数currentThreadId()、currentThread()返回当前正在执行的线程的标识符。前者返回线程的特定于平台的 ID,后者返回一个 QThread 指针。

信号槽和线程

QObject::connect()的最后一个参数将指定连接类型:

  • Qt::DirectConnection:直接连接意味着槽函数将在信号发出的线程直接调用
  • Qt::QueuedConnection:队列连接意味着向接受者所在线程发送一个事件,该线程的事件循环将获得这个事件,然后之后的某个时刻调用槽函数
  • Qt::BlockingQueuedConnection:阻塞的队列连接就像队列连接,但是发送者线程将会阻塞,直到接受者所在线程的事件循环获得这个事件,槽函数被调用之后,函数才会返回
  • Qt::AutoConnection:自动连接(默认)意味着如果接受者所在线程就是当前线程,则使用直接连接;否则将使用队列连接
  • Qt::UniqueConnection:唯一连接,对于一对信号槽,执行多次相应的connect函数,只会连接一次,在connect中判断如果是唯一连接且已经连接会直接返回;否则连接多次,发出一次信号,调用多次槽函数。

注意在上面每种情况中,发送者所在线程都是无关紧要的!在自动连接情况下,Qt 需要查看信号发出的线程是不是与接受者所在线程一致,来决定连接类型。注意,Qt 检查的是信号发出的线程,而不是信号发出的对象所在的线程!

在程序中,一般将处理任务的部分与管理线程的部分分离。简单来说,我们可以像QT官方文档中记录的第一种方法一样,利用一个QObject的子类,使用QObject::moveToThread()改变其线程依附性,而不是继承QThread类,在类中重写run函数。

主线程即main函数的线程,即GUI线程,QCoreApplication创建了代表主线程的QThread对象。

不能有两个线程同时访问一个QObject对象,除非这个对象的内部数据都已经很好地序列化(例如为每个数据访问加锁)。记住,在你从另外的线程访问一个对象时,它可能正在处理所在线程的事件循环派发的事件!基于同样的原因,你也不能在另外的线程直接delete一个QObject对象,相反,你需要调用QObject::deleteLater()函数,这个函数会给对象所在线程发送一个删除的事件。

QThread源码

QThread继承自QObject,主要的成员包括:

  • currentThreadId()、currentThread()、sleep()等静态函数;
  • started()和finished()两个信号;
  • exit()、strat()、terminate()、quit()等控制线程的函数;
  • run()和exec()两个受保护的函数。
class Q_CORE_EXPORT QThread : public QObject
{
    Q_OBJECT
public:
    static Qt::HANDLE currentThreadId() noexcept Q_DECL_PURE_FUNCTION;
    static QThread *currentThread();
    static int idealThreadCount() noexcept;
    static void yieldCurrentThread();

    explicit QThread(QObject *parent = nullptr);
    ~QThread();
    enum Priority {//................};

    void setPriority(Priority priority);Priority priority() const;
    void requestInterruption(); bool isInterruptionRequested() const;
    void setStackSize(uint stackSize);uint stackSize() const;

    QAbstractEventDispatcher *eventDispatcher() const;
    void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher);
    bool event(QEvent *event) override;int loopLevel() const;

	bool isFinished() const;bool isRunning() const;
	void exit(int retcode = 0);

public Q_SLOTS:
    void start(Priority = InheritPriority);void terminate();void quit();

public:
    bool wait(unsigned long time = ULONG_MAX);
    static void sleep(unsigned long);static void msleep(unsigned long);static void usleep(unsigned long);

Q_SIGNALS:
    void started(QPrivateSignal);void finished(QPrivateSignal);

protected:
    virtual void run();int exec();
    static void setTerminationEnabled(bool enabled = true);

protected:
    QThread(QThreadPrivate &dd, QObject *parent = nullptr);
private:
    Q_DECLARE_PRIVATE(QThread)
    friend class QCoreApplication;
    friend class QThreadData;
};

1、start方法:
qthread_win.cpp中,可以看到start()的定义。

  1. d->isInFinish 为真时,对私有类中的互斥量d->mutex进行解锁、等待和重新加锁的操作。
  2. 如果线程已经处于运行状态,直接返回,否则对d指针的标志位进行初始化。
  3. 调用系统api创建线程,赋值给d指针的handle对象。
  4. 配置调度优先级(winAPI:SetThreadPriority)。
  5. 恢复线程运行(winAPI:ResumeThread)。
void QThread::start(Priority priority)
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
//在 d->isInFinish 为真时,对 d->mutex 进行解锁、等待和重新加锁的操作。
    if (d->isInFinish) {
        locker.unlock();
        wait();
        locker.relock();
    }

    if (d->running)
        return;
//d指针的数据初始化
    d->running = true;
    d->finished = false;
    d->exited = false;
    d->returnCode = 0;
    d->interruptionRequested = false;

//调用系统api创建线程,赋值给d指针的handle对象
#if defined(Q_CC_MSVC) && !defined(_DLL)
    d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
                                            this, CREATE_SUSPENDED, &(d->id));
#else
    // MSVC -MD or -MDd or MinGW build
    d->handle = CreateThread(nullptr, d->stackSize,
                             reinterpret_cast<LPTHREAD_START_ROUTINE>(QThreadPrivate::start),
                             this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
#endif // Q_OS_WINRT

    if (!d->handle) {
        qErrnoWarning("QThread::start: Failed to create thread");
        d->running = false;
        d->finished = true;
        return;
    }

    int prio;
    d->priority = priority;
    switch (d->priority) {
    //********省略****************给prio赋值
    }
//调度级别配置
    if (!SetThreadPriority(d->handle, prio)) {
        qErrnoWarning("QThread::start: Failed to set thread priority");
    }
//创建时处于挂起状态,需要恢复线程执行
    if (ResumeThread(d->handle) == (DWORD) -1) {
        qErrnoWarning("QThread::start: Failed to resume new thread");
    }
}

在创建线程的函数CreateThread中,调用了QThreadPrivate::start(),在QThreadPrivate::start()中调用了QThread::run()函数。

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept
{	QThread *thr = reinterpret_cast<QThread *>(arg);
    //...............
    emit thr->started(QThread::QPrivateSignal());
    thr->run();
}

2、run方法
调用run()后进入所创建的子线程,每一个QThread都会默认开启一个事件循环,且只作用于当前所处的线程内

void QThread::run()
{
    (void) exec();
}

QThread::exec()方法是一个阻塞调用,它会一直阻塞当前线程,直到事件循环终止。因此,在调用 exec() 之后的代码将无法执行,直到事件循环退出。

该函数的主要功能和QCoreApplication::exec()一样,是创建一个事件循环,并执行eventLoop.exec()开启事件循环。只不过前者是子线程中的事件循环,后者是主线程即UI线程的事件循环。在QEventLoop ::exec()中调用QEventLoop ::processEvents()检查事件调度器(在QThreadPrivate::start里创建)是否存在,若存在则通过QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)处理该线程内的事件。

int QThread::exec()
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    d->data->quitNow = false;
    if (d->exited) {
        d->exited = false;
        return d->returnCode;
    }
    locker.unlock();
//创建开启事件循环
    QEventLoop eventLoop;
    int returnCode = eventLoop.exec();

    locker.relock();
    d->exited = false;
    d->returnCode = -1;
    return returnCode;
}

事件循环可以嵌套,在exec()函数中创建事件循环后,可以通过实例化QEventLoop对象创建子事件循环(QMessageBox::exec()、QDialog::exec()也会新建子事件循环并启动),会中断父事件循环的exec(),但是不会中断界面响应,因为父循环的大部分事件也会包含在子循环中。

例如使用QNetworkAccessManager提交网络请求时,由于Qt的网络操作类是异步、非阻塞的,但是又需要在当前函数中直接获得返回值时,则可以使用QEventLoop阻塞运行,可以添加定时器实现超时。

QEventLoop eventLoop;
connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
QByteArray replyData = reply->readAll();

3、quit()方法
告知线程的事件循环退出。等效于调用 QThread::exit(0)

void QThread::quit()
{ exit(); }

4、exit()方法
停止的是事件处理。
在这个线程中不再启动 QEventLoop

void QThread::exit(int returnCode)
{
    Q_D(QThread);
    d->data->quitNow = true;
    //对事件循环器遍历进行逐一清理
    for (int i = 0; i < d->data->eventLoops.size(); ++i) {
        QEventLoop *eventLoop = d->data->eventLoops.at(i);
        eventLoop->exit(returnCode);
    }
}

5、terminate()方法
调用winAPI:TerminateThread。此功能很危险,不鼓励使用。线程可以在其代码路径中的任何点终止。线程可以在修改数据时终止。线程没有机会自行清理、解锁任何持有的互斥锁等。简而言之,仅在绝对必要时使用此功能。

void QThread::terminate()
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    if (!d->running)
        return;
    if (!d->terminationEnabled) {
        d->terminatePending = true;
        return;
    }
#ifndef Q_OS_WINRT
    TerminateThread(d->handle, 0);
#endif
    QThreadPrivate::finish(this, false);//只发送finnish信号
}

QThreadPrivate源码

主要成员包括一个互斥量,一个QThreadData对象,running、finished、exited等标志位。

和QT中其它类和私有类的关系类似,QThread主要提供了线程管理的接口,在接口函数中主要完成了两个工作:

  • 创建并启动事件循环器;
  • 改变私有类的标志位,并调用私有类的同名函数和QThreadData成员进行深层次的执行操作。

私有类创建事件调度器,在QThreadData中启动。

class QThreadPrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(QThread)
public:
    QThreadPrivate(QThreadData *d = nullptr);
    ~QThreadPrivate();
    void setPriority(QThread::Priority prio);

    mutable QMutex mutex;
    QAtomicInt quitLockRef;

    bool running;
    bool finished;
    bool isInFinish; //when in QThreadPrivate::finish
    std::atomic<bool> interruptionRequested;

    bool exited;
    int returnCode;

    uint stackSize;
    QThread::Priority priority;

    static QThread *threadForId(int id);

    static unsigned int __stdcall start(void *) noexcept;
    static void finish(void *, bool lockAnyway=true) noexcept;

    Qt::HANDLE handle;
    unsigned int id;
    int waiters;
    bool terminationEnabled, terminatePending;

    QThreadData *data;
    static QAbstractEventDispatcher *createEventDispatcher(QThreadData *data);

    void ref(){quitLockRef.ref();}
    void deref(){//....... }
};

1、createEventDispatcher创建事件调度器

新建一个事件调度器,在QThreadData的同名函数中启动。

QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{
    Q_UNUSED(data);
    return new QEventDispatcherWin32;
}

2、QThreadPrivate::start

  1. 分配线程本地存储 (TLS) 索引
  2. 创建事件调度器并启动
  3. 发射started()信号
  4. run()函数创建事件循环器,启动线程运行
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept
{
    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadData *data = QThreadData::get2(thr);

    qt_create_tls();
    TlsSetValue(qt_current_thread_data_tls_index, data);
    data->threadId.storeRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));

    QThread::setTerminationEnabled(false);

    {
        QMutexLocker locker(&thr->d_func()->mutex);
        data->quitNow = thr->d_func()->exited;
    }

    data->ensureEventDispatcher();
    emit thr->started(QThread::QPrivateSignal());
    QThread::setTerminationEnabled(true);
    thr->run();

    finish(arg);
    return 0;
}

3、QThreadPrivate::finish

  1. 发射finish信号
  2. 释放线程本地存储
  3. 删除事件调度器
  4. 调用winAPI:CloseHandle关闭线程
void QThreadPrivate::finish(void *arg, bool lockAnyway) noexcept
{
    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadPrivate *d = thr->d_func();

    QMutexLocker locker(lockAnyway ? &d->mutex : 0);
    d->isInFinish = true;
    d->priority = QThread::InheritPriority;
    void **tls_data = reinterpret_cast<void **>(&d->data->tls);
    locker.unlock();
    emit thr->finished(QThread::QPrivateSignal());
    QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    QThreadStorageData::finish(tls_data);
    locker.relock();

    QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.loadRelaxed();
    if (eventDispatcher) {
        d->data->eventDispatcher = 0;
        locker.unlock();
        eventDispatcher->closingDown();
        delete eventDispatcher;
        locker.relock();
    }

    d->running = false;
    d->finished = true;
    d->isInFinish = false;
    d->interruptionRequested = false;

    if (!d->waiters) {
        CloseHandle(d->handle);
        d->handle = 0;
    }

    d->id = 0;
}

QThreadDate源码

主要成员包括事件循环栈、post事件队列、QThread对象、事件调度器和quitNow、canWait等标志位。

每个线程的主要数据都保存在该类中,通过访问该类来操作线程相关数据。

class QThreadData
{
public:
    QThreadData(int initialRefCount = 1);
    ~QThreadData();

    static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
    static void clearCurrentThreadData();
    
    static QThreadData *get2(QThread *thread)
    { Q_ASSERT_X(thread != nullptr, "QThread", "internal error"); return thread->d_func()->data; }

    void ref();void deref();
    inline bool hasEventDispatcher() const{ return eventDispatcher.loadRelaxed() != nullptr; }
    QAbstractEventDispatcher *createEventDispatcher();
    QAbstractEventDispatcher *ensureEventDispatcher()
    {
        QAbstractEventDispatcher *ed = eventDispatcher.loadRelaxed();
        if (Q_LIKELY(ed))
            return ed;
        return createEventDispatcher();
    }

    bool canWaitLocked(){//....}
    // This class provides per-thread (by way of being a QThreadData member) storage for qFlagLocation()
    class FlaggedDebugSignatures {//...};
private:
    QAtomicInt _ref;

public:
    int loopLevel;
    int scopeLevel;

    QStack<QEventLoop *> eventLoops;
    QPostEventList postEventList;
    QAtomicPointer<QThread> thread;
    QAtomicPointer<void> threadId;
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;

    bool quitNow;
    bool canWait;
    bool isAdopted;
    bool requiresCoreApplication;
};

创建事件调度器函数,调用线程私有类的同名函数进行创建,然后启动创建好的事件调度器。

QAbstractEventDispatcher *QThreadData::createEventDispatcher()
{
    QAbstractEventDispatcher *ed = QThreadPrivate::createEventDispatcher(this);
    eventDispatcher.storeRelease(ed);
    ed->startingUp();
    return ed;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值