QT多线程研究与分析

一、概述

线程可以看作是cpu运行的基本的基本单位,进程可以看作是运行资源的基本单位。程序的一次执行就可以看作是一个进程。进程中又包含了许多的线程,进程之间的内存不可以共享,线程之间共享进程的内存。在程序中,线程是独立的、并发的执行流。

多线程一般用于在多任务分发处理过程中,为了任务的调度,并发执行而采用的技术,创建线程执行任务可以提高程序执行性能,提高CPU使用效率,而且单CPU多线程是时间轮片的切换,多CPU可以真正的做到多CPU同时工作。

二、特点

1、程序地址空间共享
2、代码段共享,数据不能共享
3、多线程单核会有线程切换,多核没有仅仅是数据处理就可以
4、用多线程既能快速处理数据又能快速进行计算

5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现.

三、缺点

多线程有优点,同时也具有缺点,大体表现在以下几个方面:

  1. 死锁 (两个线程相互等待对方释放资源)
  2. 乱序
  3. 并发访问数据造成的问题(问题查错困难)
  4. 低效率

四、线程的创建方式

1.继承QThread

自定义线程类,需要添加<QThread>头文件,在自定义类中重写run函数,主要线程代码就在run里实现,如果需要等待触发完成的,需要加上exec()让事件进入循环。

代码示例:

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();

  }

2.继承QObject

继承QObject自定义类,在需要使用线程的类头文件中要加上QThread头文件,并创建线程成员对象或者指针,然后在调用的地方创建线程对象,并连接响应信号,并使用moveThread函数将当前对象移动到线程中使用,然后调用开始启动,此方法Qt比较推荐的一种。

示例代码:

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

  };

3.继承QRunnable

QRunnable是所有可执行对象的基类。我们可以继承QRunnable,并重写虚函数void QRunnable::run () ,来实现线程的创建,但是一般这种都是配合线程池来使用的类,QThreadPool构建的参数就是QRunnable,设置setAutoDelete (true),执行完成自动删除。

示例1代码:

#include <QRunnable>

#include <QThread>

#include <QThreadPool>

#include <QDebug>

class CurrentTask : public QRunnable

{

     // 线程执行任务:每间隔1s打印出线程的信息

         void run()

         {

             for (int nCount = 0; nCount < 5; nCount++)

             {

                            qDebug() << QThread::currentThread();

                            QThread::msleep(1500);

             }

         }

};

int main(int argc, char *argv[])

{

         QCoreApplication a(argc, argv);

 

         QThreadPool    threadpool;                    // 构建一个本地的线程池

         threadpool.setMaxThreadCount(3);         // 线程池中最大的线程数

    for (int nNum = 0; nNum < 150; nNum++)

         {

             CurrentTask    *task = new CurrentTask(this);    // 循环构建可在线程池中运行的任务

task->setAutoDelete(true);

             threadpool.start(task);      //线程池分配一个线程运行该任务

             QThread::msleep(1500);

         }

         return a.exec();

}

示例1代码执行实现简单的任务,如果需要使用信号与槽通信,则需要继承QObject,

如示例2:

#include <QRunnable>

#include <QThread>

#include <QThreadPool>

#include <QDebug>

class CurrentTask: public QObject, public QRunnable

{

    Q_OBJECT

// 自定义信号

signals:

    void finished();

 

public:

     void run() {

         qDebug() << "Hello Thread : " << QThread::currentThreadId();

         emit finished();

     }

};

int main(int argc, char *argv[])

{

         QCoreApplication a(argc, argv);

 

         QThreadPool    threadpool;                    // 构建一个本地的线程池

         threadpool.setMaxThreadCount(3);         // 线程池中最大的线程数

    for (int nNum = 0; nNum < 150; nNum++)

         {

             CurrentTask    *task = new CurrentTask(this);    // 循环构建可在线程池中运行的任务

                   task->setAutoDelete(true);

             threadpool.start(task);      //线程池分配一个线程运行该任务

             QThread::msleep(1500);

         }

         return a.exec();

}

注意:如果要在run函数里需要实现tcpsocket的收发需要加入QEventLoop循环,否则将不会触发,自动断开连接,当你任务完成可以写退出循环的槽连接quit()退出事件。

4.使用QtConcurrent::run

         QtConcurrent命名空间提供了很多方法可以实现并发编程,需要加入头文件#include <QtConcurrent>,其次在pro中加入QT += concurrent模块,可以实现多线程方式,

示例代码:

void func(QString str)

{

qDebug() << __FUNCTION__ << str << QThread::currentThreadId() << QThread::currentThread();

}

void Widget::onBtnWriteClicked()

{

    QFuture<void> f1 =QtConcurrent::run(func,QString("aa"));

    f1.waitForFinished();

}

5. Qt invokeMethod 异步

Qt中提供了一个便捷的函数QMetaObject::invokeMethod,方便我们异步调用,类似线程的作用。

函数原型:

bool QMetaObject::invokeMethod(QObject *obj, const char *member,

                            Qt::ConnectionType type,

                            QGenericReturnArgument ret,

                            QGenericArgument val0 = QGenericArgument(nullptr),

                            QGenericArgument val1 = QGenericArgument(),

                            QGenericArgument val2 = QGenericArgument(),

                            QGenericArgument val3 = QGenericArgument(),

                            QGenericArgument val4 = QGenericArgument(),

                            QGenericArgument val5 = QGenericArgument(),

                            QGenericArgument val6 = QGenericArgument(),

                            QGenericArgument val7 = QGenericArgument(),

                            QGenericArgument val8 = QGenericArgument(),

                            QGenericArgument val9 = QGenericArgument())

此函数用于调用对象的成员(信号或插槽)。如果可以调用成员,则返回true。如果没有此类成员或参数不匹配,则返回false。

QMetaObject::invokeMethod除上文这个函数以外还有5个重载函数,这里不再赘述。

参数说明:

obj:被调用对象的指针

member:成员方法的名称

type:连接方式,默认值为 Qt::AutoConnection

Qt::DirectConnection,则会立即调用该成员。(同步调用)

Qt::QueuedConnection,则会发送一个QEvent,并在应用程序进入主事件循环后立即调用该成员。(异步调用)

Qt::BlockingQueuedConnection,则将以与Qt :: QueuedConnection相同的方式调用该方法,除了当前线程将阻塞直到事件被传递。使用此连接类型在同一线程中的对象之间进行通信将导致死锁。(异步调用)

Qt::AutoConnection,则如果obj与调用者位于同一个线程中,则会同步调用该成员; 否则它将异步调用该成员。

ret:接收被调用函数的返回值

val0~val9:传入被调用函数的参数,最多十个参数

注:必须要使用Q_RETURN_ARG()宏来封装函数返回值、Q_ARG()宏来封装函数参数。

示例代码:

bool result;

//同步调用

QMetaObject::invokeMethod(obj, "func", Qt::DirectConnection,

                          Q_RETURN_ARG(bool, result),

                          Q_ARG(QString, "test"),

                          Q_ARG(int, 100);

//异步调用

QMetaObject::invokeMethod(obj, "func", Qt::QueuedConnection,

                          Q_ARG(QString, "test"),

                          Q_ARG(int, 100);

注意:使用Qt::QueuedConnection异步调用,将无法获取返回值,因为此连接方式只是负责把事件交给事件队列,然后立刻返回,所以,函数返回值就无法确定了。但,我们可以使用上文提及的Qt:: DirectConnection连接方式,这个连接方式会阻塞发射信号的线程一直等到队列连接槽返回后,才会恢复阻塞,这样就可以保证我们能得到函数的返回值。

猜你喜欢

转载自blog.csdn.net/leiyang2014/article/details/119519209
今日推荐