QThread的应用(一)

一、QThread的应用背景

    QT是基于事件驱动的架构的,一般在开发QT的应用程序时,为了将耗时的计算工作与主线程分离,以避免界面窗口容易出现卡死或未响应的问题,QThread和QTimer的使用则能很好的实现该类需求。此外,QT的库随着时代的发展,引入了很多有用的函数,比如通过一个全局QMutex来实现工作计算流程的暂停。另外QMutexLocker可以更简单的实现QMutex线程锁的功能,详细可以参看QT5.8或更高版本的QT帮助说明书。这里主要记录了使用QThread时遇到的问题和具体的解决思路。

二、QThread的使用

    QThread主要有两种使用方式,第一种是通过继承QObject建立一个包含工作处理信号和槽的Worker类对象,随后采用moveToThread的方式;另一种是直接继承QThread,重写run函数的方式。QT官方更加推荐第一种方式,因为这种方式更加灵活,可以将Worker类的槽跟任何对象、任何其他线程的信号相连接,由于存在队列连接(queued connections)的机制,这种方式在不同线程中的连接是安全的。

     QThread的使用需要引入一下头文件:

#include <QThread>  //或者#include "QThread"

2.1 QObject方式

    以下的代码是帮助文档中,我们可以通过下图的关系来理解,其中的Controller类,一般可以替换成我们QT中MainWindowl类,这样可以实现主线程与新建的线程之间的通讯。

   

 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); //将work对象移到一个新的线程中
          //线程结束时自动释放线程中的worker对象
          connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
           //Controller的信号与Worker的槽连接
          connect(this, &Controller::operate, worker, &Worker::doWork);
           //Worker的信号与Controller的槽连接
          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 &);
  };

注意:

线程的退出除了使用quit(),还可以使用terminate(),但是terminate()会立刻强制终止或随后终止,这依赖于操作系统的策略。需要添加wait()函数以确保终止。当这个线程被terminate()终止时,所有等待该线程的线程都会被破坏,因此该函数存在危险并不鼓励使用。此外,采用termininate()终结线程是在任何位置的,比如这就会导致在修改数据时被终止,而线程没有机会去关闭文件和释放锁等。

2.2继承QThread方式

 class WorkerThread : public QThread
  {
      Q_OBJECT
      void run() Q_DECL_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();
  }

    采用这种形式的方式很容易出现跨线程调用对象的错误,另外MainWindow中的ui相关控件的设置必须要在主线程中,不然会出现不同程度的错误。这是因为采用这种方式,这个线程在run函数return之后就会结束,除非你调用exec(),否则不会进行事件循环。而跨线程调用对象也可以采用connect()函数的第五个参数,但是这样做需要仔细检查是否安全,所有鼓励采用第一种方式使用QThread.

三、线程的同步

    当多个线程需要访问同一个资源时,为了保证计算的正确性需要线程进行顺序化的访问,QT提供了QMutex基础类来实现锁的功能,此外还有QSemaphore,QWaitCondition来实现低层次的线程同步,而采用队列连接的方式则是一个无死锁(死锁指的是A线程在等待B线程的资源释放,B线程也在等待A线程的资源释放,导致A和B线程均在一直等待)的方式。

3.1 QMutex与QMutexLocker

    QMutex是一个强制互斥(mutex)的基础类。一个线程通过锁定一个mutex来获得一个共享的资源(变量、文件等),当另一个线程尝试锁定这个资源时,如果这个资源已经被第一个线程锁定,则第二线程就会被阻塞,直到第一个线程使用unlock()释放资源。

下列的例子是用来说明锁的作用的最简单的例子:

 int number = 6;

  void method1()
  {
      number *= 5;
      number /= 4;
  }

  void method2()
  {
      number *= 3;
      number /= 2;
  }

如果上述的例子正常调用的结果如下:

// method1()
  number *= 5;        // number is now 30
  number /= 4;        // number is now 7

  // method2()
  number *= 3;        // number is now 21
  number /= 2;        // number is now 10

但是如果有两个线程同时访问这两个函数,则会导致如下的可能结果

// Thread 1 calls method1()
  number *= 5;        // number is now 30

  // Thread 2 calls method2().
  //
  // Most likely Thread 1 has been put to sleep by the operating
  // system to allow Thread 2 to run.
  number *= 3;        // number is now 90
  number /= 2;        // number is now 45

  // Thread 1 finishes executing.
  number /= 4;        // number is now 11, instead of 10

而使用mutex之后,则可以正常运行:

 QMutex mutex;
  int number = 6;

  void method1()
  {
      mutex.lock();
      number *= 5;
      number /= 4;
      mutex.unlock();
  }

  void method2()
  {
      mutex.lock();
      number *= 3;
      number /= 2;
      mutex.unlock();
  }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值