Qt-QThread

qt帮助:

        QThread 类提供了一种与平台无关的方法来管理线程。
        QThread 对象管理程序中的一个控制线程。 QThreads 在 run() 中开始执行。 默认情况下,run() 通过调用 exec() 启动事件循环,并在线程内运行 Qt 事件循环。
        您可以通过使用 QObject::moveToThread() 将工作对象移动到线程来使用它们。

实例1:

        暂时不考虑多线程,先思考这样一个问题:想想我们平时会把耗时操作代码放在哪里?一个类中。那么有了多线程后,难道我们要把这段代码从类中剥离出来单独放到某个地方吗?显然这是很糟糕的做法。QObject 中的 moveToThread() 函数可以在不破坏类结构的前提下依然可以在新线程中运行。

preview 假设现在我们有个 QObject 的子类 Worker,这个类有个成员函数doWork(),该函数中运行的代码非常耗时。此时我要做的就是将这个类对象“移动”到新线程里,这样 Worker 的所有成员函数就可以在新线程中运行了。那么如何触发这些函数的运行呢?信号槽。在主线程里需要有个 signal 信号来关联并触发 Worker 的成员函数,与此同时 Worker 类中也应该有个 signal 信号用于向外界发送运行的结果。这样思路就清晰了,Worker 类需要有个槽函数用于执行外界的命令,还需要有个信号来向外界发送结果。如下列代码

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 &);
  };

        在作为“外界”的 Controller 类中,由于要发送命令与接收结果,因此同样是有两个成员:operate() 信号用于启动 Worker 类的耗时函数运行,handleResults() 槽函数用于接收新线程的运行结果。注意别和 Worker 类的两个成员搞混了,在本例中信号对应着槽,即“外界”的信号触发“新线程”的槽,“外界”的槽接收“新线程”的信号结果。

        Worker slot内的代码将在单独的线程中执行。 但是,您可以自由地将 Worker 的slot连接到来自任何对象、任何线程中的任何信号。 由于一种称为queued connections的机制,跨不同线程连接信号和槽是安全的。

        “移动到新线程”是一个很形象的描述,作为入门的认知是可以的,但是它的本质是改变线程亲和性(也叫关联性)。为什么要强调这一点?这是因为如果你天真的认为 Worker 类对象整体都移动到新线程中去了,那么你就会本能的认为 Worker 类对象的控制权是由新线程所属,然而事实并不是如此。「在哪创建就属于哪」这句话放在任何地方都是适用的。Worker 类对象是在 Controller 类中创建并初始化,因此该对象是属于主线程的。而 moveToThread() 函数的作用是将槽函数在指定的线程中被调用。当然,在新线程中调用函数的前提是该线程已经启动处于就绪状态,所以在上一节的 Controller 构造函数中,我们把各种信号槽连接起来后就可以启动新线程了。

        使用 moveToThread() 有一些需要注意的地方,首先就是类对象不能有父对象,否则无法将该对象“移动”到新线程。如果类对象保存在栈上,自然销毁由操作系统自动完成;如果是保存在堆上,没有父对象的指针要想正常销毁,需要将线程的 finished() 信号关联到 QObject 的deleteLater() 让其在正确的时机被销毁。其次是该对象一旦“移动”到新线程,那么该对象中的计时器(如果有 QTimer 等计时器成员变量)将重新启动。不是所有的场景都会遇到这两种情况,但是记住这两个行为特征可以避免踩坑。

        使代码在单独的线程中运行的另一种方法是继承 QThread 并重新实现 run()。 例如:

实例2:

class WorkerThread : public QThread
  {
      Q_OBJECT
      void run() override {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }
     
      void do(){}//主线程
  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);
      connect(this, &MyObject::do, workerThread, &WorkerThread ::do);
      workerThread->start();
  }

为了验证只有run() 在新线程里运行,我们还写了一个do(),如果执行workerThread->do();则和run的线程id不同;do()在老的线程中执行;

        如果重写run()函数,则不会启动子线程的消息循环,需要自行执行exec();否则子线程收不到主线程发送的信号:如上面的

    connect(this, &MyObject::do, workerThread, &WorkerThread ::do);

则不会执行

源码:

class Q_CORE_EXPORT QThread : public QObject
{
    Q_OBJECT
public:
    static Qt::HANDLE currentThreadId() Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION;
    static QThread *currentThread();
    static int idealThreadCount() Q_DECL_NOTHROW;
    static void yieldCurrentThread();

    explicit QThread(QObject *parent = Q_NULLPTR);
    ~QThread();

    //优先级
    enum Priority {
        IdlePriority,

        LowestPriority,
        LowPriority,
        NormalPriority,
        HighPriority,
        HighestPriority,

        TimeCriticalPriority,

        InheritPriority
    };

    void setPriority(Priority priority);
    Priority priority() const;

    bool isFinished() const;
    bool isRunning() const;

    void requestInterruption();
    bool isInterruptionRequested() const;

    void setStackSize(uint stackSize);
    uint stackSize() const;

    void exit(int retcode = 0);

    QAbstractEventDispatcher *eventDispatcher() const;
    void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher);

    bool event(QEvent *event) Q_DECL_OVERRIDE;
    int loopLevel() const;

public Q_SLOTS:
    void start(Priority = InheritPriority);//线程启动函数
    void terminate();
    void quit();//线程退出函数

public:
    // default argument causes thread to block indefinetely
    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 = Q_NULLPTR);

private:
    Q_DECLARE_PRIVATE(QThread)

    friend class QCoreApplication;
    friend class QThreadData;
};

QThread::start

void QThread::start(Priority priority)
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);

    if (d->isInFinish) {
        locker.unlock();
        wait();
        locker.relock();
    }

    if (d->running)
        return;

    d->running = true;
    d->finished = false;
    d->exited = false;
    d->returnCode = 0;
    d->interruptionRequested = false;


    //创建线程,线程函数是:QThreadPrivate::start
    /*
      NOTE: we create the thread in the suspended state, set the
      priority and then resume the thread.

      since threads are created with normal priority by default, we
      could get into a case where a thread (with priority less than
      NormalPriority) tries to create a new thread (also with priority
      less than NormalPriority), but the newly created thread preempts
      its 'parent' and runs at normal priority.
    */
#if defined(Q_CC_MSVC) && !defined(_DLL) // && !defined(Q_OS_WINRT)
#  ifdef Q_OS_WINRT
    // If you wish to accept the memory leaks, uncomment the part above.
    // See:
    //  https://support.microsoft.com/en-us/kb/104641
    //  https://msdn.microsoft.com/en-us/library/kdzttdcb.aspx
#    error "Microsoft documentation says this combination leaks memory every time a thread is started. " \
    "Please change your build back to -MD/-MDd or, if you understand this issue and want to continue, " \
    "edit this source file."
#  endif
    // MSVC -MT or -MTd build
    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 = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start,
                                            this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
#endif // Q_OS_WINRT

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

    //设置优先级
    int prio;
    d->priority = priority;
    switch (d->priority) {
    case IdlePriority:
        prio = THREAD_PRIORITY_IDLE;
        break;

    case LowestPriority:
        prio = THREAD_PRIORITY_LOWEST;
        break;

    case LowPriority:
        prio = THREAD_PRIORITY_BELOW_NORMAL;
        break;

    case NormalPriority:
        prio = THREAD_PRIORITY_NORMAL;
        break;

    case HighPriority:
        prio = THREAD_PRIORITY_ABOVE_NORMAL;
        break;

    case HighestPriority:
        prio = THREAD_PRIORITY_HIGHEST;
        break;

    case TimeCriticalPriority:
        prio = THREAD_PRIORITY_TIME_CRITICAL;
        break;

    case InheritPriority:
    default:
        prio = GetThreadPriority(GetCurrentThread());
        break;
    }

    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");
    }
}

  QThreadPrivate::start(void *arg)

nsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadData *data = QThreadData::get2(thr);

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

    QThread::setTerminationEnabled(false);

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

    if (data->eventDispatcher.load()) // custom event dispatcher set?
        data->eventDispatcher.load()->startingUp();
    else
        createEventDispatcher(data);//创建 事件分发器

#if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) && !defined(Q_OS_WINRT)
    // sets the name of the current thread.
    QByteArray objectName = thr->objectName().toLocal8Bit();
    qt_set_thread_name((HANDLE)-1,
                       objectName.isEmpty() ?
                       thr->metaObject()->className() : objectName.constData());
#endif

    emit thr->started(QThread::QPrivateSignal());//发射线程启动信号
    QThread::setTerminationEnabled(true);
    thr->run();//调用QThread::run()函数 -- 线程函数

    finish(arg);
    return 0;
}

实际上 run() 函数是在这里调用的,并且发出了 started() 启动信号,等到 run() 函数执行完毕,最后是调用了 QThreadPrivate::finish 函数结束线程,并且在finish内会发出 QThread::finished() 线程已结束的信号。

在start中创建了eventDispatcher用于消息循环

void QThreadPrivate::createEventDispatcher(QThreadData *data)
{
#ifndef Q_OS_WINRT
    QEventDispatcherWin32 *theEventDispatcher = new QEventDispatcherWin32;
#else
    QEventDispatcherWinRT *theEventDispatcher = new QEventDispatcherWinRT;
#endif
    data->eventDispatcher.storeRelease(theEventDispatcher);
    theEventDispatcher->startingUp();
}

QThread::run()

/*!
    The starting point for the thread. After calling start(), the
    newly created thread calls this function. The default
    implementation simply calls exec().

    You can reimplement this function to facilitate advanced thread
    management. Returning from this method will end the execution of
    the thread.

    \sa start(), wait()
*/
void QThread::run()
{
    (void) exec();
}

/*!
    Enters the event loop and waits until exit() is called, returning the value
    that was passed to exit(). The value returned is 0 if exit() is called via
    quit().

    This function is meant to be called from within run(). It is necessary to
    call this function to start event handling.

    \sa quit(), exit()
*/
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;
}

在eventLoop.exec中进入消息循环:

int QEventLoop::exec(ProcessEventsFlags flags)
{
......

    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.load();
}
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher.load())
        return false;
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

在这里便用到了之前创建的eventDispatcher;

每一个 Qt 应用程序至少有一个 事件循环 ,就是调用了 QCoreApplication::exec() 的那个事件循环。不过,QThread也可以开启事件循环。只不过这是一个受限于线程内部的事件循环。因此我们将处于调用main()函数的那个线程,并且由 QCoreApplication::exec() 创建开启的那个事件循环成为 主事件循环 ,或者直接叫 主循环 。注意,QCoreApplication::exec()只能在调用main()函数的线程调用。主循环所在的线程就是主线程,也被成为 GUI 线程,因为所有有关 GUI 的操作都必须在这个线程进行。QThread的局部事件循环则可以通过在 QThread::run() 中调用 QThread::exec() 开启。

我们通过以上源码可以看到,它的定义很简单,就是调用了一个函数:QThread::exec() 开启线程中的 事件循环 ,我们也可以通过继承QThread,重写run()函数的方式,让其实现相对复杂的逻辑代码。如果你的线程需要将某些槽函数在本线程完成的话,就必须开启事件循环,否则在线程内无法响应各种依赖事件循环的信号并作出相应的行为。

事件循环
若使用默认的 run() 方法或自行调用 exec() ,则QThread将开启事件循环。QThread 同样提供了 exit() 函数和 quit() 槽。这赋予了QThread使用需要事件循环的非GUI类的能力(QTimer、QTcpSocket 等)。也使得该线程可以关联任意一个线程的信号到指定线程的槽函数。如果一个线程没有开启事件循环,那么该线程中的 timeout() 将永远不会发射。

如果在一个线程中创建了OBject 对象,那么发往这个对象的事件将由该线程的事件循环进行分派。

在这里插入图片描述


        传统图形界面应用程序都只有一个线程执行,并且一次执行一个操作。如果用户调用一个比较耗时的操作,就会冻结界面响应。一个解决方法是按照事件处理的思路:调用 Void QApplication::processEvents() 或 void QApplication::processEvents ( int maxtime ) 来强迫事件循环进行,但是这种做法是有潜在风险的。

        按照QCoreApplication:processEvents()可能会引起递归,导致栈溢出崩溃的说法,当主线程在某个槽函数里正在执行processEvents时,刚好有一个能响应此槽函数的信号发送过(肯定是其他线程发的信号),这时就可能会发生可怕的递归,导致栈溢出崩溃。原因是processEvents在处理自己槽函数的事件时,又会调用到processEvents,进入到无尽的递归中。

另外一个解决方法是:采用多线程。

        QT QThread多线程编程的方法一直有个争议,就是Bradley T. Hughes:You’re doing it wrong归纳为3中方法优劣问题:

方法1:
不使用事件循环。这是官方的 Manual 、example 以及相关书籍中都介绍的一种的方法。

子类化 QThread;
重载 run 函数,run函数内有一个 while 或 for 的死循环;
设置一个标记为来控制死循环的退出。
方法2:
这种方法也是Bradley T. Hughes极力批判的。

子类化 QThread;
重载 run 使其调用 QThread::exec();
并为该类定义信号和槽,这样一来,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用 moveToThread(this);而争论和不解正是这样的一条语句造成的。
Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

方法3:
在Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。而从Qt4.4开始,qthreads-no-longer-abstract ,run 默认调用 QThread::exec() 。这样一来不需要子类化 QThread 了,只需要子类化一个 QObject 就够了,这正是被 Bradley T. Hughes推荐的方法。

类似于:

QThread thread;

Object obj;

Dummy dummy;

obj.moveToThread(&thread);

QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));

thread.start();

回到问题的本质,我们想要新开一个子线程来完成耗时的操作,有两个地方可以实现:

(1)子类化QThread的run()函数里,在run()函数里执行耗时的操作。因为run()函数是子线程的入口函数,一定不和主线程在同一个线程。就是方法1介绍的。如果在QThread子类里的slot里执行耗时操作,因为slot是在主线程中执行的,所以行不通。

(2)利用方法(3),子类化QObject ,如object。在object的slot里执行耗时操作。但是slot仍在主线程里执行,怎么办?QThread *thread;object.moveToThread(thread); 这样,这个QObject的slot都会在子线程里执行,达到了和主线程区分开的目的啦。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt中释放QThread的主要步骤如下: 1. 停止线程的执行: 首先,需要调用QThread的`quit()`或`exit()`方法,来停止线程的执行。这会导致线程的事件循环退出。 2. 等待线程的结束: 使用`wait()`方法来等待线程的结束。这会阻塞主线程,直到线程完成执行并退出。 3. 销毁QThread对象: 最后,调用`deleteLater()`方法来销毁QThread对象。这会将销毁的过程放在事件队列中,以确保在适当的时候销毁对象。 下面是一个示例代码,展示了如何释放QThread: ```python # 创建一个自定义的线程类 class MyThread(QThread): def run(self): # 线程的执行逻辑 pass # 创建线程对象 thread = MyThread() # 启动线程 thread.start() # 停止线程的执行 thread.quit() thread.wait() # 销毁QThread对象 thread.deleteLater() ``` 以上代码展示了如何释放QThread。首先通过调用`quit()`方法停止线程的执行,然后使用`wait()`方法等待线程的结束,最后通过`deleteLater()`方法销毁QThread对象。这样可以确保线程的安全退出和对象的正确销毁。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [python GUI库图形界面开发之PyQt5线程类QThread详细使用方法](https://download.csdn.net/download/weixin_38648037/13737127)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [QtQThread介绍(常用接口及实现、自动释放内存、关闭窗口时停止线程运行、同步互斥)](https://blog.csdn.net/qq_14945437/article/details/98868606)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值