QT线程例程之理解

官方原文说明

The QThread class provides a platform-independent way to manage threads.
A QThread object manages one thread of control within the program. QThreads begin executing in run(). By default, run() starts the event loop by calling exec() and runs a Qt event loop inside the thread.

You can use worker objects by moving them to the thread using QObject::moveToThread().
The code inside the Worker’s slot would then execute in a separate thread. However, you are free to connect the Worker’s slots to any signal, from any object, in any thread. It is safe to connect signals and slots across different threads, thanks to a mechanism called queued connections.

Another way to make code run in a separate thread, is to subclass QThread and reimplement run(). For example:代码1
In that example, the thread will exit after the run function has returned. There will not be any event loop running in the thread unless you call exec().

我的理解

如下
QThread class是一个与平台无关的线程管理类。在程序中可以使用QThread对象来管理一个线程,QThreads对象一启动后就开始执行.run()中的代码,.run()默认情况下是通过调用exec()来开始在该线程内部执行QT事件循环(注:如果run执行返回了,那么该线程也就结束了)。此后就是介绍两种线程的实现方法了

QT提供了两种线程的实现方法,一种是继承重定义(subclass),另一种是通过QObject对象的moveToThread()方法实现,qt称之为worker-object方法

  1. 线程继承重定义

    • 首先,QThreads::run是一个虚方法(virtul),因此你可subclass一个QThread,然后重定义run来实现自己想要执行的代码,然后就是对线程的启动,退出之类的操作了。上面说了,线程一启动后就会执行run()方法。此处你重定义后就执行你写的代码了,然后发送resultReady,退出线程,如果仍想线程继续执行事件循环,你需要调用exec()方法。
      这里要说明下的是:当你将finished信号与deleteLater关联起来时,在退出线程时会释放所有线程中的对象资源,因此在下次重启线程时你需要再次申请线程中的对象,否则就会出现些segment fault之类的错误。
    • 代码1

        class WorkerThread : public QThread//继承线程类
        {
            Q_OBJECT
            void run() Q_DECL_OVERRIDE {//重定义实现代码
                QString result;
                /* ... 线程启动后要执行的线程代码 */
                emit resultReady(result);
            }
        signals:
            void resultReady(const QString &s);
        };
      
        void MyObject::startWorkInAThread()//主线程
        {
            WorkerThread *workerThread = new WorkerThread(this);//实例化一个线程
            //线程处理后通知主线程执行handleResults
            connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
            //线程结束时自删对象资源
            connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
            workerThread->start();//开始线程
        }
  2. worker-object

    • 自己定义一个worker-object(继承自QObject),在该对象里实现自己想要执行的代码,然后通过譔对象的moveToThread()方法,将其提交一个QThread对象,此后你只需发射信号通知worker-object对象执行槽函数,那该槽函数就会自行在QThread管理的分线程中执行。特别要说明的是worker-object中的槽函数是在另一个独立的线程中执行的。虽然如此,但worker-object中的槽函数仍然可以与任何线程,任何对象中的任何信号关联。这也是QT信号与槽的强大之处。
      重要的事情强调三便以上:槽函数,是以信号和槽函数的形式才能在另一线程上执行,如果直接在主线程上以对象方法调用的方式,那依然是在主线程上执行哟。
      简言之,你定义可两个对象A,B,在定义一个线程对象C,其中对象A在当前主线程上运行,另一给对象B你提交给线程对象C去处理,A,B间的信号与槽可相互关联,但执行B中的任何槽函数时,都会交由C线程去执行之。
      在此处,A,C两个对象的所处的线程都是主线程,B对象的所处的线程是C线程。
      worker-object表述图
    • 代码2

      class Worker : public QObject //工作对象
       {
           Q_OBJECT
      
       public slots:
           void doWork(const QString &parameter) {
               QString result;
               /* ... 想在在线程中实现的代码 ... */
               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 &);
       };
      
    • 上面的workerThread.start()只是启动了workerThread线程的事件循环队列(参数方法1的描述),并没有执行worker::doWork,你需要在主线程上emit operate才会执行哟。如果还想多几个函数在分线程上执行,你可依此定义Worker的SLOT函数和主线程中的信号关联就好,当然,在doWork上执行循环语句时,下次再emit operate必须等上次的执行结束才有效,同理,要结束这个线程那也必须要等doWork循环语句执行完才能结束,所以如果用到了循环语句,必须要考虑在结束线程时如何退出循环。
  3. 注意事项:
    继承重定义方法中,WorkerThread 对象的slot函数,在主线程中发出执行信号后,其执行仍是在实例化它的主线程MyObject上执行,而不是你认为的在子线程上执行。
    简单举例:

          class WorkerThread : public QThread
          {
              Q_OBJECT
              void run() Q_DECL_OVERRIDE {
                  QString result;
                  qDegug()<<"执行run 函数的线程号:"<<QThread::currentThreadId(;
                  emit resultReady(result);
              }
          public slot:
              void test(void){
                qDegug()<<"执行slot函数的线程号:"<<QThread::currentThread();        
              }
          signals:
              void resultReady(const QString &s);
          };
    
          void MyObject::startWorkInAThread()//主线程
          {
              WorkerThread *workerThread = new WorkerThread(this);//实例化一个线程
              //线程处理后通知主线程执行handleResults
              connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
              //线程结束时自删对象资源
              connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
              connect(this, SIGNAL(doTest), workerThread, &WorkerThread::test);
              qDegug()<<"main线程号:"<<QThread::currentThreadId();
              workerThread->start();//开始线程
              emit doTest();          
          }

    执行后可以看出SLOT所在线程是跟main线程一致的而不是与RUN所在的分线程相同。因为执行run函数时,WorkerThread是自行创建了一个新线程去调用run的。而其它函数则仍是在创建它的线程上调用的。QT说明原因如下:

    a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run().This means that all of QThread’s queued slots will execute in the old thread.

    还不是很明白?那再贴一段:

    Like other objects, QThread objects live in the thread where the object was created – not in the thread that is created when QThread::run() is called. It is generally unsafe to provide slots in your QThread subclass, unless you protect the member variables with a mutex.

    上面说的很明白了,QThread objects是存在于创建它的线程上,它的所有执行都在该线程上,但当调用run时,QThread objects会创建一个新的线程去执行它。(子类化的QThread的所有资源方法都是在主线程上,除非你是在run方法中创建的。)

    worker-object方法中,因为整个worker对象都通过movetoThread转移到线程上了,所以,在线程启动后,worker对象的所有槽函数的发射执行(调用执行不是)都是在分线程中执行的。

    记住,当使用线程继承方法时,必须要明白,该继承的对象的run方法是在新线程上执行的同时,该对象的槽函数却仍是在主线程上执行。如果其成员变量在两类函数上都有使用,必须检查该变量的安全性(就是变量的在不同线程间的同步问题了)。

    故推存使用worker-object

Threads and QObjects总结

  1. 谁创建,谁执行。(object在哪个线程上创建,则其函数在哪个线程上执行)

    • 不允许在一个线程中创建object,而在另一线程中调用该object的方法
    • 在线程销毁前必须保证线程中的对象都被释放了。
    • 如果一个线程没有被启动,那线程中的对象的任何方法都无法被调用。(如果被调用成功,那肯定不在你指望的线程中。)
    • 在worker-object中,由于worker-object本身是在主线程中创建的,所以依此规则,直接调用该对象的槽函数仍在主线程中执行,只有emit signal,分线程在even loop中接收到该消息后才执行slot function.
  2. 每个线程各自维护一个独立的event_loop ;

    • event_loop的执行靠调用exec();
    • event_loop的退出要调用:quit或exit方法
      这里写图片描述
  3. 创建后的对象可通过movetoThread转移到其它线程,断绝与当前线程的关系。

    • QObject 的线程会被子对象继承下来,因此move一个对象时,它的子对象也被move了。
    • 如果一个对象有parent,那该对象无法move,因为其父对象的线程与你要移动到的线程不一致。QT保证了一个对象的所以子对象同在一个线程中。
  4. QThread对象的run函数是在一个单独的线程上执行的,而不是创建QThread对象的线程上执行。

    • 由于QThread::run函数和QThread上的slot函数是在不同线程上执行的,所以使用QThread 的slot并不安全,可能QThread的成员同时在两个线程上使用造成冲突。
  5. slotsignal的连接方式:

    • Direct Connection
      signal被发射后,立即在发射signal的线程中调用slot函数
    • Queued Connection
      signal被发射后,在控制由接收signal的线程的event_loop接管后,调用slot。
    • Blocking Queued Connection
      它与上一种的区别是,当前发射线程被阻塞,直到slot返回后再次激活执行。
    • Auto Connection (default)
      当接收方和发射方处同一线程,则类似Direct Connection,否则就是Queued Connection,在调用connect时,默认使用此参数。
    • Unique Connection
      与自动连接方式相同,但其不能重复执行相同的连接,否则返回false.

应用

参照worker-object的代码创建线程时,一个还好,多个线程就受不了了,每次都要在主线程里写一堆的start,quit,wait,如果线程里有个循环条件,还需要在主线程里将此条件置假才能正常退出线程,这样就相当麻烦,还不如用subclass方式, 为此自己使用如下变种,测试了是可行的。

class Worker : public QObject //对象的创建和销毁还是在主线程执行。
 {
     Q_OBJECT
     bool doLoop;
 public:   
     Worker(){
        moveToThread(&workerThread);
        workerThread.start();
     }
     ~Worker(){
        doLoop = false;
        workerThread.quit();
        workerThread.wait();//为保安全退出一定要加上。
     }     
     QThread workerThread;    
 public slots:
     void doWork(const QString &parameter) {
         QString result;
         doLoop = true;
         while(doLoop&&workerThread.isRunning())
         {
             /* ... 想在在线程中实现的代码 ... */
         }
         emit resultReady(result);
     }
     void doWork_other(const QString &parameter); 
 signals:
     void resultReady(const QString &result);
 };


 class Controller : public QObject
 {
     Q_OBJECT     
 public:
     Controller() {
         Worker *worker = new Worker;
         Worker *worker_ohter = new Worker;
         connect(this, &Controller::operate, worker, &Worker::doWork);
         connect(worker, &Worker::resultReady, this, &Controller::handleResults);
         connect(this, &Controller::operate_other, worker_ohter, &Worker::doWork_other);
         connect(worker_ohter, &Worker::resultReady, this, &Controller::handleResults);

     }
     ~Controller()
     {
         delete worker;
         delete worker_other;
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
     void operate_other(const QString &);
 };

这样子是不是省事很多?主线程只管创建对象,connect信号与槽就好,至于线程的结束那些就交管线程对象本身去管理好了,这样不容易弄混结束条件,而且由于线程thread是Worker对象的一个成员,因此可以直接用thread.isRuning 等这些线程执行情况去判断,同时,成员线程退出时不能去释放创建它的对象,所以不要再关联QThread::finished和QObject::deleteLater了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值