精华内容
下载资源
问答
  • 前面在pyqt5多线程(QThread)遇到的坑(一)中提到了先实例化类,再把实例对象传参到线程类,这样的确实可行,但是还是遇到了新坑。 pyqt5多线程(QThread)遇到的坑(一 起因 被实例化的类是做数据处理的,传入和...
  • QThread

    2021-08-25 14:38:13
    QThread 类提供了一种与平台无关的方法来管理线程。 QThread 对象管理程序中的一个控制线程。 QThreads 在 run() 中开始执行。 默认情况下,run() 通过调用 exec() 启动事件循环,并在线程内运行 Qt 事件循环。 您...

    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都会在子线程里执行,达到了和主线程区分开的目的啦。

    展开全文
  • QThread是我们将要详细介绍的第一个类。它也是 Qt 线程类中最核心的底层类。由于 PyQt 的跨平台特性,QThread要隐藏掉所有平台相关的代码。
  • Qt5_QThread_信号传递

    2020-10-10 21:07:50
    由于只有UI主线程才可以操作桌面UI,那么在多线程中,其它线程的数据若要显示出来,可以通过全局共用变量来实现,或者通过线程通信的方式来实现,本例程就是通过UI上的按钮启动线程,在线程中发送信号到UI主线程,并...
  • 主要介绍了PyQt5中多线程模块QThread使用方法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 用PyQt中的QThread类实现多线程 利用PyQt中的pyqtSignal类实现信息的触发和捕获,即定义事件和订阅事件 利用QThread类实现多线程的方法 1,新建一个python类,继承自QThread from PyQt5.QtCore import QThread ...
  • QThread是Qt的线程类中最核心的底层类。由于PyQt的的跨平台特性,QThread要隐藏所有与平台相关的代码 要使用的QThread开始一个线程,可以创建它的一个子类,然后覆盖其它QThread.run()函数 class Thread...
  • Qt中使用QThread来管理线程,一个QThread对象,就是一个线程。QThread对象也有消息循序exec()函数,用来处理自己这个线程的事件。 Qt实现多线程有两种方式 ​1、Qt第一种创建线程方式 首先要继承QThread ...
  • pyqt5--Qthread解决窗口未响应问题,用来解决python后台爬数据时窗口未响应问题。使用这样的结构可以实现后台爬数据的同时实时UI显示,克服假死问题,
  • QT5线程QThread使用示例

    2018-12-10 21:19:01
    本示例采用继承QThread的方式创建线程,在创建的子线程中计数,并将计数的数值通过信号与槽的方式发送至主线程,在主线程的UI界面上显示出来。
  • pyqt5_QThread.zip

    2020-03-23 17:21:23
    使用pycharm+anaconda开发环境,在pyqt5中使用QThread实现多线程,前后端分离
  • 自己做了一个tcp工具,在学习动画的...class BSJTcpThread(QtCore.QThread): recv_signal = QtCore.pyqtSignal(str) send_signal = QtCore.pyqtSignal(str) def __init__(self, socketcp, onBtn, heartcheck, senBtn
  • QThread资料

    2018-09-21 16:37:06
    本资源是关于QThread如何正确使用的资源,里面有篇国外分析的资料,讲的很好
  • Qt 使用多线程QThread实现进度条
  • Qthread_moveTothread.rar

    2020-02-15 15:26:57
    参照Gt5官方说明文档,在控制台中实现了一个交互终端,编写了线程类,封装了QThread,将耗时函数类Worker采用moveToThread方法放到QThread线程中。并通过命令字控制线程的开启和关闭。
  • QThread介绍

    2021-03-17 11:20:20
    Qt对多线程操作有着完整的支持,Qt中通过继承QThread并重写run()方法的方式实现多线程代码的编写。针对线程之间的同步与互斥问题,Qt还提供了QMutex、QReadWriteLock、QwaitCondition、QSemaphore等多个类来实现。 ...

    在程序设计中,为了不影响主程序的执行,常常把耗时操作放到一个单独的线程中执行。Qt对多线程操作有着完整的支持,Qt中通过继承QThread并重写run()方法的方式实现多线程代码的编写。针对线程之间的同步与互斥问题,Qt还提供了QMutex、QReadWriteLock、QwaitCondition、QSemaphore等多个类来实现。

    本篇博客将针对以下几个方面进行讲解

    [1]QThread的常用接口以及QThread的实现

    [2]QThread的信号事件

    [3]QThread执行完后自动释放内存

    [4]关闭窗口时自动停止线程的运行

    [5]QThread的同步与互斥
    [1]QThread的常用接口以及QThread的实现

    定义Qthread需要执行的任务:

    virtual void run()

    编程者需要重写run()函数,在run函数里来实现线程需要完成的任务。

    开始执行线程任务:

    [slot] void QThread::start(QThread::Priority priority = InheritPriority)

    线程休眠:

        //以下三个函数全部是静态成员函数
        void  msleep(unsigned long msecs)
        void  sleep(unsigned long secs)
        void  usleep(unsigned long usecs)

    结束线程执行:

    在run函数里主动结束:

        void  quit()
        void  exit(int returnCode = 0)

    在任何位置强制线程结束:

    [slot] void QThread::terminate()

    不推荐此方法,除非万不得已。在调用此方法后还需调用wait()方法,来等待线程结束并回收资源。

    线程优先级相关:

        //获取线程的优先级
        QThread::Priority  priority() const
         
        //设置线程的优先级
        void  setPriority(QThread::Priority priority)

    判断是否运行:

        //判断是否运行结束
        bool  isFinished() const
         
        //判断是否正在运行
        bool  isRunning() const

    QThread具体实现:

    在这里通过模拟一个耗时的任务来进行说明,在QThread中模拟一个下载任务(每100ms计数+1,直到加到100为止),并在界面上通过QLabel显示出当前下载进度。实现一个自定义QThread的步骤如下:

    ①新创建类TestThread继承QThread

    ②重写run方法

    ③定义TestThread对象并调用该对象的start方法运行

    TestThread.h代码如下:

        #ifndef TESTTHREAD_H
        #define TESTTHREAD_H
         
        #include <QObject>
        #include <QThread>
         
        class TestThread : public QThread
        {
            Q_OBJECT
        public:
            explicit TestThread(QObject *parent = nullptr);
         
        private:
            //重写run方法
            void run();
         
        signals:
            //定义信号
            void ShowDownloadProgress(int progress);
         
        public slots:
        };
         
        #endif // TESTTHREAD_H

    TestThread.cpp代码如下:

        #include "testthread.h"
         
        TestThread::TestThread(QObject *parent) : QThread(parent)
        {
         
        }
         
        void TestThread::run()
        {
            for(int i = 0 ; i <= 100 ; i++)
            {
                QThread::msleep(100);
                ShowDownloadProgress(i);
            }
        }

    其中,在run中进行线程任务的实现,当run函数执行完了,整个线程也就运行结束了。在run函数中用msleep来模拟耗时的过程,用i++来模拟下载进度的增加。每一次循环都会发出ShowDownloadProgress(i)信号,通过信号与槽的绑定,可以在Qt处理线程中完成QLabel数据的更新。

    widget.cpp中线程对象的创建、信号与槽的绑定、线程启动代码如下:

        TestThread *thread = new TestThread(this);
        connect(thread,SIGNAL(ShowDownloadProgress(int)),this,SLOT(ProgressLabelShow(int)));
        thread->start();

    ProgressLabelShow(int)槽函数的具体实现如下:

        void Widget::ProgressLabelShow(int prog)
        {
            ui->ProgressLabel->setText(QString::number(prog) + "%");
        }

    如上代码即实现了在界面上实时显示下载进度。之所以通过发出信号通知Qt处理线程,并在Qt处理线程中完成QLabel显示内容的更新是因为多线程同时操作Qt控件会有一定的危险,有可能导致程序的异常。而在TestThread线程中发出信号通知Qt处理线程,并在Qt处理线程中操作Qt控件的方法无论是在代码稳定性还是代码结构上都是最佳的。

    运行效果:

    [2]QThread的信号事件

    QThread有两个信号事件,一个是线程开始时(run函数被调用之前发出此信号),发出来的,一个是线程结束时(在线程将要结束时发出此信号)。开始和结束信号如下:

        void finished()
        void started()

    [3]QThread执行完后自动释放内存

    QThread执行结束后自动释放内存,是利用finished信号实现的。官方提供的手册的finished信号的介绍中有这样一句话:

        When this signal is emitted, the event loop has already stopped running. No more events will be processed in the thread, except for deferred deletion events. This signal can be connected to QObject::deleteLater(),to free objects in that thread.

    这句话的意思是将finished绑定到QObject::deleteLater()槽函数可以实现线程的自动销毁。

    为了便于看到效果,我们给自定义的TestThread 类加上析构函数,并在里面打印提示信息:

        ~TestThread()
        {
            qDebug() << "~TestThread";
        }

    在widget.cpp中绑定finished信号与QObject::deleteLater():

        TestThread *thread = new TestThread(this);
        connect(thread,SIGNAL(ShowDownloadProgress(int)),this,SLOT(ProgressLabelShow(int)));
        connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()));
        thread->start();

    其中,信号的发送者和接收者都是新创建的thread对象,槽函数为deleteLater(),该槽函数是继承自QObject的。

    程序执行结果:

    可以看到析构函数被自动执行,由此就完成了在线程结束后自动释放线程空间的功能。
    [4]关闭窗口时自动停止线程的运行

    前面有讲到在线程运行结束时自动释放线程控件,然而,在窗口关闭时。为了及时释放系统资源,也需要程序自动停止正在运行的线程,并释放掉空间。通过重写widget类的closeEvent方法可以实现这个目的:

    改写TestThread类如下:

        #ifndef TESTTHREAD_H
        #define TESTTHREAD_H
         
        #include <QObject>
        #include <QThread>
        #include <QDebug>
         
        class TestThread : public QThread
        {
            Q_OBJECT
        public:
            explicit TestThread(QObject *parent = nullptr);
            ~TestThread()
            {
                qDebug() << "~TestThread";
            }
         
            void StopThread();
         
        private:
            //重写run方法
            void run();
            bool stopFlag = false;
         
        signals:
            //定义信号
            void ShowDownloadProgress(int progress);
         
        public slots:
        };
         
        #endif // TESTTHREAD_H

     

        #include "testthread.h"
         
        TestThread::TestThread(QObject *parent) : QThread(parent)
        {
         
        }
         
        void TestThread::run()
        {
            for(int i = 0 ; i <= 100 && !stopFlag ; i++)
            {
                QThread::msleep(100);
                ShowDownloadProgress(i);
            }
        }
        void TestThread::StopThread()
        {
            stopFlag = true;
        }

    其中,新加的stopFlag标志是为了控制线程是否结束,提供StopThread供外部调用。

    在widget.cpp中重写closeEvent方法:

        void Widget::closeEvent(QCloseEvent *event)
        {
            qDebug() << "closeEvent";
            TestThread *thread =  this->findChild<TestThread*>();
            if(thread == nullptr)
                return;
            if(thread->isRunning())
            {
                thread->StopThread();
                thread->wait();
            }
        }

    在closeEvent中直接调用findChild方法得到先前创建的TestThread线程的指针,然后调用StopThread方法将线程的结束标志置为true,最后调用wait方法阻塞等待线程结束。

    运行结果如下:

     
    [5]QThread的同步与互斥

    在多线程编程中,常常会有某些资源被多个线程共用的情况。例如多个线程需要读/写同一个变量,或者一个线程需要等待另一个线程先运行后才可以运行。进程的同步与互斥,在多线程编程中尤为重要。用的好了,既能让程序稳定运行,又能不影响程序运行效率。用的不好就可能导致程序虽然在稳定运行,但效率大大下降。究其原因,编程者在编程时要明确知道应该用什么同步互斥机制,如何去用这些同步互斥机制。对于线程的同步与互斥Qt提供了QMutex、QReadWriteLock、QwaitCondition、QSemaphore等多个类来实现。

    互斥锁:

    QMutex是基于互斥量的线程同步类,QMutex类主要提供了以下几个方法,用于实现互斥操作:

        lock():上锁,如果之前有另一个进程也针对当前互斥量进行了上锁操作,则此函数将一直阻塞等待,直到解锁这个互斥量。

        unlock():解锁,与lock()成对出现。

        tryLock():尝试解锁一个互斥量,该函数不会阻塞等待,成功返回true,失败返回false(其他线程已经锁定了这个互斥量);

    下面是一个利用互斥量来实现的例子:

        int flag;
        QMutex mutex;
         
        void threadA::run()
        {
            ....
            mutex.lock();
            flag = 1;
            mutex.unlock();
            ....
        }
         
        void threadB::run()
        {
            ....
            mutex.lock();
            flag = 2;
            mutex.unlock();
            ....
        }
         
        void threadC::run()
        {
            ....
            mutex.lock();
            flag = 3;
            mutex.unlock();
            ....
        }

    利用互斥锁保护的资源,不允许多个线程同时操作。

    读写锁:

    互斥锁会在某些应用中出现问题,例如多个线程需要去读某一个变量。此时是不需要排队的,可以同时进行读操作。如果用互斥锁来做保护,这会导致不必要的排队现象发生,影响到程序的运行效率。这时,就需要引入读写锁QReadWriteLock。

    QReadWriteLock提供了以下几个方法:

    lockForRead():以只读方式锁定资源,其他线程可读(可以调用lockForRead),不可写(调用lockForWrite将阻塞等待)。如果先前有其他线程以写锁方式进行了锁定,则调用这个函数会阻塞等待

    lockForWrite():以写入方式锁定资源,其他线程不可读,不可写。如果先前有其他线程以读锁或写锁的方式进行了锁定,调用这个函数会阻塞等待。

    unlock()解锁,与锁定资源函数成对出现。

    tryLockForRead():lockForRead的非阻塞版本。

    tryLockForWrite():lockForWrite的非阻塞版本。

    下面是一个用读写锁的例子:

        int flag;
        QReadWriteLock rwLock;
         
        void threadA::run()
        {
            ....
            rwLock.lockForWrite();
            flag = 1;
            rwLock.unlock();
            ....
        }
         
        void threadB::run()
        {
            ....
            rwLock.lockForWrite();
            flag = 2;
            rwLock.unlock();
            ....
        }
         
        void threadC::run()
        {
            ....
            rwLock.lockForRead();
            switch(flag)
            {
                ......
            }
            rwLock.unlock();
            ....
        }
        void threadD::run()
        {
            ....
            rwLock.lockForRead();
            qDebug() << flag;
            ......
            rwLock.unlock();
            ....
        }

    利用读写锁保护的资源,允许多个线程同时读,不允许多个线程在读的同时写,不允许在写的同时读或写。

    基于QWaitCondition的线程同步:

     前面所提到的互斥锁、读写锁,都是通过加锁的方式实现的资源的保护。在资源解锁时,其他线程并不会立刻得到通知。针对这个问题,Qt引入了QWaitCondition类。将QWaitCondition与QMutex或QReadWriteLock相结合可以实现在资源解锁后及时通知并唤醒其他等待进程。

    QWaitCondition提供的方法如下:

        wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX)
        wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = ULONG_MAX):解锁互斥锁或读写锁,并阻塞等待被唤醒。当被唤醒后,重新锁定QMutex或QReadWriteLock

        wakeAll():唤醒所有等待的进程,顺序不确定,由操作系统调度
        wakeOne():唤醒一个等待的进程,唤醒哪一个不确定,由操作系统调度

    QWaitCondition常用于生产/消费者中,一个产生数据的,几个消费数据的。比如键盘的输入,当键盘输入数据后,有多个线程同时对键盘输入的数据做不同的处理,此时就需要用到QWaitCondition来实现。

    全局可用变量的定义

        QWaitCondition keyPressed;
        char c;
        int count;

    线程1:获取键盘的输入

        for(;;){
              c = getchar();
         
              mutex.lock();
              // Sleep until there are no busy worker threads
              while (count > 0) {
                  mutex.unlock();
                  sleep(1);
                  mutex.lock();
              }
              keyPressed.wakeAll();
              mutex.unlock();
          }

    线程2:处理输入数据

         for(;;){
              mutex.lock();
              keyPressed.wait(&mutex);
              ++count;
              mutex.unlock();
         
              do_something_xxxx(c);
         
              mutex.lock();
              --count;
              mutex.unlock();
          }

    线程3:处理输入数据

         for(;;){
              mutex.lock();
              keyPressed.wait(&mutex);
              ++count;
              mutex.unlock();
         
              do_something_xxxxxxxxxxxxx(c);
         
              mutex.lock();
              --count;
              mutex.unlock();
          }

    在本例的线程1中引入了count 是否大于 0的判断,是为了保证每个线程都能够执行完后,再进行键盘输入获取以及唤醒操作。

    利用信号量(QSemaphore)实现的线程同步:

    互斥锁、共享锁都只能针对一个资源进行保护,而不能针对多个类似的资源进行保护。而利用QSemaphore可以做到对多个类似的资源进行保护。

    QSemaphore主要提供了以下几个方法:

        acquire(int n = 1):获取n个资源,如果没有,则阻塞等待,直到有n个资源可用为止。

        release(int n = 1):释放更多资源,如果信号量的资源已全部可用后,调用此函数将增加更多的资源

        bool tryAcquire(int n = 1):尝试获取n个资源,不会阻塞等待,有返回true,无返回false

    简单示例:

        QSemaphore sem(5);      // sem.available() == 5
         
        sem.acquire(3);         // sem.available() == 2
        sem.acquire(2);         // sem.available() == 0
        sem.release(5);         // sem.available() == 5
        sem.release(5);         // sem.available() == 10
         
        sem.tryAcquire(1);      // sem.available() == 9, returns true
        sem.tryAcquire(250);    // sem.available() == 9, returns false

    示例:

    定义的全局变量

        const int DataSize = 100000;
         
        const int BufferSize = 8192;
        char buffer[BufferSize];
         
        QSemaphore freeBytes(BufferSize);
        QSemaphore usedBytes;

    生产者线程:

        class Producer : public QThread
          {
          public:
              void run() override
              {
                  for (int i = 0; i < DataSize; ++i) {
                      freeBytes.acquire();
                      buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
                      usedBytes.release();
                  }
              }
          };

    消费者线程:

        class Consumer : public QThread
          {
              Q_OBJECT
          public:
              void run() override
              {
                  for (int i = 0; i < DataSize; ++i) {
                      usedBytes.acquire();
                      fprintf(stderr, "%c", buffer[i % BufferSize]);
                      freeBytes.release();
                  }
                  fprintf(stderr, "\n");
              }
          };

    这个示例展示了生产者要产生10万个数据,并循环放进8192大小的缓存区中,消费者同时去取缓存区数据。在生产者放的过程中,只能放置到未使用的空间或经过消费者处理过的空间中。

    信号量的引入保证了数据的读写的效率,也保证了消费者能够完整的拿到所有数据。而此例如果用互斥锁或读写锁实现的话效率将大打折扣(生产者:上锁(等待)----写满缓冲区-----解锁   消费者:上锁(等待)-----读缓冲区-----解锁),针对一个有多个字节的数据缓冲区读写不能同时进行。而使用信号量一边写未被写过的或已经被处理过的空间,一边将已写过的空间交给读进程操作将使程序效率大大提高。
     

    展开全文
  • 对于航拍图像序列,结合Qt中Qthread,多线程并发拼接。分组处理。可使用NPU-DRONEMAP进行实验,提供一种解决思路
  • QT线程QThread的推荐用法 在 代码示例中, QT线程单次运行一个函数,推荐用QtConcurrent::run() 在另一个线程中运行一个函数
  • qt4.7以前QThread 版本的使用示例。供大家参考.
  • qthread示例

    2016-07-20 15:30:10
    代码演示了qthread的使用,细心的朋友可以在workthread对象和thread对象的成员方法中打断点,看看其代码执行时所在的线程
  • 小程序包括信号与槽的内容,多线程通信及vs中添加qt类的方法
  • Qthread

    2020-10-18 21:59:02
    QThread详解 回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿。。。 一、线程管理 1、线程启动void start...

    随笔 - 607  文章 - 0  评论 - 21  QThread详解 回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿。。。
    一、线程管理
    1、线程启动void start(Priority priority = InheritPriority)调用后会执行run()函数,但在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。优先级参数的效果取决于操作系统的调度策略。特别是那些不支持线程优先级的系统优先级将会被忽略(例如在Linux中,更多细节请参考http://linux.die.net/man/2/sched_setscheduler)。 2、线程执行int exec()进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则范围0。virtual void run();线程的起点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个功能,便于管理自己的线程。该方法返回时,该线程的执行将结束。 3、线程退出void quit()告诉线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。void exit(int returnCode = 0)告诉线程事件循环退出。调用这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,按照惯例0表示成功,任何非0值表示失败。void terminate()终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait()确保万无一失。当线程被终止后,所有等待中的线程将会被唤醒。警告:此功能比较危险,不鼓励使用。线程可以在代码执行的任何点被终止。线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。。。总之,只有在绝对必要时使用此功能。建议:一般情况下,都在run函数里面设置一个标识符,可以控制循环停止。然后才调用quit函数,退出线程。 4、线程等待void msleep(unsigned long msecs)强制当前线程睡眠msecs毫秒void sleep(unsigned long secs)强制当前线程睡眠secs秒void usleep(unsigned long usecs)强制当前线程睡眠usecs微秒bool wait(unsigned long time = ULONG_MAX);线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。 5、线程状态    bool isFinished() const        线程是否结束    bool isRunning() const        线程是否正在运行 6、线程优先级    void setPriority(Priority priority)    这个函数设置正在运行线程的优先级。如果线程没有运行,此功能不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。    优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。    Priority priority() const    下面来看下优先级中的各个枚举值:ConstantValueDescriptionQThread::IdlePriority0没有其它线程运行时才调度.QThread::LowestPriority1比LowPriority调度频率低.QThread::LowPriority2比NormalPriority调度频率低.QThread::NormalPriority3操作系统默认的默认优先级.QThread::HighPriority4比NormalPriority调度频繁.QThread::HighestPriority5比HighPriority调度频繁.QThread::TimeCriticalPriority6尽可能频繁的调度.QThread::InheritPriority7使用和创建线程同样的优先级. 这是默认值.  二、主线程、次线程在Qt之线程(QThread)一节中我介绍了QThread 的两种使用方法:1、子类化 QThread(不使用事件循环)。这是官方手册、例子以及相关书籍中都介绍的一种常用的方法。a. 子类化 QThreadb. 重载 run 函数,run函数内有一个while或for的死循环(模拟耗时操作)c. 设置一个标记为来控制死循环的退出。 2、子类化 QObjecta. 子类化 QObjectb. 定义槽函数c. 将该子类的对象moveToThread到新线程中 run 对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。采用这两种做法,毫无疑问都会在次线程中运行(这里说的是,run中的逻辑以及子类化QObject后连接通过moveToThread然后连接到QThread的started()信号的槽函数,这个下面会详细讲解)。那么,线程中的槽函数是怎么运行的呢?说到信号与槽,大家应该再熟悉不过了,包括我,特别喜欢使用自定义信号与槽,感觉用起来特方便、特棒。。。经常使用,你能否100%的使用正确?你了解它的高级用法吗?1、你是否在多次connect,还发现不了为什么槽函数会执行那N多次。2、你是否了解disconnect3、你是否了解connect中的第五个参数 Qt::ConnectionType关于connect、disconnect信号、槽的使用可参考:Qt之信号与槽。既然谈到线程这里需要重点说下Qt::ConnectionType(信号与槽的传递方式)ConstantValueDescriptionQt::AutoConnection0自动连接:(默认值)如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。Qt::DirectConnection1直接连接:当信号发射时,槽函数将直接被调用。无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。Qt::QueuedConnection2队列连接:当控制权回到接受者所依附线程的事件循环时,槽函数被调用。槽函数在接收者所依附线程执行。也就是说:这种方式既可以在线程内传递消息,也可以跨线程传递消息Qt::BlockingQueuedConnection3与Qt::QueuedConnection类似,但是会阻塞等到关联的slot都被执行。这里出现了阻塞这个词,说明它是专门用来多线程间传递消息的。 举例:MyObject.h#ifndef MYOBJECT_H
    #define MYOBJECT_H

    #include

    class MyObject : public QObject
    {
    Q_OBJECT

    public:
    explicit MyObject(QObject *parent = 0);

    public slots:
    void start();
    };

    #endif // MYOBJECT_HMyObject.cpp#include “MyObject.h”
    #include
    #include

    MyObject::MyObject(QObject *parent)
    QObject(parent)
    {

    }

    void MyObject::start()
    {
    qDebug() << QString(“my object thread id:”) << QThread::currentThreadId();
    }
    main.cpp#include “MyObject.h”
    #include
    #include
    #include

    int main(int argc, char *argv[])
    {
    QApplication a(argc, argv);

    qDebug() << QString("main thread id:") << QThread::currentThreadId();
    
    MyObject object;
    QThread thread;
    object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()));
    thread.start();
    
    return a.exec();
    

    } 查看运行结果:  “main thread id:” 0xf08  “my object thread id:” 0x216c    显然主线程与槽函数的线程是不同的(你可以多次尝试,屡试不爽。。。),因为moveToThread后MyObject所在的线程为QThread,继上面介绍的thread.start()执行后首先会发射started()信号,也就是说started()信号发射是在次线程中进行的,所以无论采取Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection哪种连接方式,主线程与槽函数的线程都是不同的。 1、修改代码如下: MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::DirectConnection);
    thread.start();查看运行结果:  “main thread id:” 0x2688  “my object thread id:” 0x2110     显然主线程与槽函数的线程是不同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::DirectConnection(无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行)。也就是说started()信号发射是在次线程中进行的,槽函数也是在次线程中进行的,所以主线程与槽函数的线程是不同的。 2、修改代码如下: MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::QueuedConnection);
    thread.start();查看运行结果:  “main thread id:” 0x24ec  “my object thread id:” 0x24ec     显然主线程与槽函数的线程是相同的,继上面介绍的Qt::QueuedConnection(槽函数在接收者所依附线程执行)。也就是说started()信号发射是在次线程中进行的,但MyObject所依附的线程为主线程(因为注释掉了moveToThread),所以主线程与槽函数的线程必然是相同的。 3、修改代码如下: MyObject object;
    QThread thread;
    //object.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::AutoConnection);
    thread.start();查看运行结果:  “main thread id:” 0x2700  “my object thread id:” 0x2700     显然主线程与槽函数的线程是相同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::AutoConnection(如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。)。因为started()信号和MyObject依附的线程不同,所以结果和Qt::QueuedConnection对应的相同,所以主线程与槽函数的线程是相同的。     基本就介绍到这里,QThread使用和上面的大同小异,run里面执行的代码都是在次线程中,如果是QThead的槽函数,那么结论同上!

    展开全文
  • 一个QThread练习例子

    2019-04-04 18:39:39
    一个QThread练习例子

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,977
精华内容 4,390
关键字:

qthread