zoukankan      html  css  js  c++  java
  • QThread与多线程(比较清楚)

    QThread类为我们提供了一种平台无关的管理线程的方式。一个QThread对象管理应用程序中的一个线程,该线程从run()函数开始执行。并且,默认情况下,我们可以在run()函数中通过调用QThread::exec()函数来在当前线程中开启一个事件循环。

    而使用QThread开启线程的最常用的方式 就是继承QThread类,重写其run()方法,因为我们刚才就说过,QThread代表的线程就是从run()函数开始运行的。

    例如:

    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();
    }
    在这个例子中,该线程会在run()函数返回后退出。又因为我们没有在run()函数中调用exec(),所以该线程中没有运行事件循环。
    另一种使用线程的方法,是将要完成的工作封装到一个工作者对象中,然后使用QObject::moveToThread()函数将该对象移动到一个线程对象中。如下:

    class Worker : public QObject
    {
    Q_OBJECT

    public slots:
    void doWork(const QString ¶meter) {
    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 &);
    };
    在这个例子中,Worker的槽函数会在一个独立的线程中运行。但是,我们可以自由的将Worker的槽函数连接到任意的信号上,任意的对象上,任意的线程中。并且,由于Qt提供的信号和槽连接类型中的queued connections类型,使我们可以安全的跨线程连接信号和槽。
    并且,很重要的一点是,QThread对象是存活在创建它的那个线程中,而不是在运行run()函数的新线程中。这意味着,连接到QThread的所有的排队型槽函数都是在旧线程中执行的。因此,如果我们希望我们调用的槽函数也在新线程中执行,就必须使用这种工作对象的方式。新的槽函数 不应该直接实现在QThread的子类中。

    还有,当子类化QThread时,要记住的一点是线程对象的构造函数在旧线程中运行,而run()在新线程中运行。所以,如果在这两个函数中都访问了一个成员变量,那么就是在两个不同的线程中访问的,要确保访问的安全性。

    下面,我们分别使用这两种方式,通过打印线程id的方法,来看一下它们的区别。
    我们先写一个工作者类,继承自QObject:

    #ifndef WORKER_H
    #define WORKER_H

    #include <QObject>
    #include <QThread>
    #include <QDebug>

    class Worker : public QObject
    {
    Q_OBJECT
    public:
    explicit Worker(QObject *parent = 0);

    public slots:
    void start();
    };

    #endif // WORKER_H

    #include "worker.h"

    Worker::Worker(QObject *parent) : QObject(parent)
    {

    }

    void Worker::start()
    {
    qDebug() << "child thread: " << QThread::currentThreadId();
    }
    我在该类中,只定义了一个start()函数,作为我们线程的执行体,其所做的工作也很简单,只是打印出本线程的id。
    接下来,再写一个Controller类,其也继承自QObject,用来控制线程的启动。

    #ifndef CONTROLLER_H
    #define CONTROLLER_H

    #include <QObject>
    #include "worker.h"

    class Controller : public QObject
    {
    Q_OBJECT
    public:
    explicit Controller(QObject *parent = 0);
    void start();

    signals:
    void operate();

    private:
    QThread workerThread;
    };

    #endif // CONTROLLER_H

    #include "controller.h"

    Controller::Controller(QObject *parent) : QObject(parent)
    {
    Worker *worker = new Worker;
    worker->moveToThread(&workerThread);
    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
    connect(this, &Controller::operate, worker, &Worker::start);
    workerThread.start();
    }

    void Controller::start()
    {
    emit operate();
    }
    在这个类中,我们在声明了一个start()函数和一个operate()信号,还有一个QThread对象。然后在构造函数中,实例化一个Worker类对象,再使用moveToThread()函数,将其移动到我们定义的线程对象中;最后,为了启动我们的执行体,我们将operate()信号连接到Worker的start()槽函数上。然后,启动我们的线程对象。
    main函数如下:

    #include <QCoreApplication>
    #include "controller.h"

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

    qDebug() << "main thread: " << QThread::currentThreadId();

    Controller controller;
    controller.start();

    return a.exec();
    }
    我们定义一个Controller的对象,然后调用其start()方法即可。而根据我们在Controller::start()的实现中,发出了operate()信号,该信号又连接到Worker::start(),所以会触发该函数的执行,即我们的线程执行体。
    其执行结果如下:

    可以看出,子线程的槽函数确实在一个独立的线程中运行。

    而如果我们用继承QThread的方法来实现该功能,则会看到不同的结果。

    先实现Thread类如下:

    #ifndef THREAD_H
    #define THREAD_H
    #include <QThread>
    #include <QDebug>

    class Thread : public QThread
    {
    Q_OBJECT
    public:
    Thread(QObject *parent = Q_NULLPTR);

    public slots:
    void Come();

    protected:
    void run() Q_DECL_OVERRIDE;
    };

    #endif // THREAD_H

    #include "thread.h"

    Thread::Thread(QObject *parent)
    :QThread(parent)
    {

    }

    void Thread::Come()
    {
    qDebug() << "child thread: " << QThread::currentThreadId();
    }

    void Thread::run()
    {
    exec();
    }
    我们继承了QThread类,重新了run()方法,这是继承QThread开启线程所必须的一步。另外,我们还定义了一个槽函数,Come(),在该函数中打印了本线程的id。为了让线程不退出,我们在run()函数中调用了QThread::exec()函数,为该线程开启一个事件循环,等待某个信号来触发Come()槽函数。
    接下来,再修改Controller类如下:

    #ifndef CONTROLLER_H
    #define CONTROLLER_H

    #include <QObject>
    #include "worker.h"

    class Controller : public QObject
    {
    Q_OBJECT
    public:
    explicit Controller(QObject *parent = 0);
    void start();

    signals:
    void operate();
    };

    #endif // CONTROLLER_H

    #include "controller.h"
    #include "thread.h"

    Controller::Controller(QObject *parent) : QObject(parent)
    {
    }

    void Controller::start()
    {
    emit operate();
    }
    我们只在start()函数中,发送了operate()信号。
    再修改main函数如下:

    #include <QCoreApplication>
    #include "controller.h"
    #include "thread.h"

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

    qDebug() << "main thread: " << QThread::currentThreadId();

    Controller controller;
    Thread thread;
    QObject::connect(&controller, SIGNAL(operate()), &thread, SLOT(Come()));
    thread.start();
    controller.start();

    return a.exec();
    }

    我们先定义了Controller和Thread对象,然后将Controller的operate()信号连接到Thread的Come()槽函数上,紧接着启动线程,等待信号。最后调用Controller的start()方法,发送我们定义的信号,该信号又会触发Thread类的槽函数,打印出线程ID。
    该种方式的执行结果如下:

    可见,线程类Thread中的槽函数并没有运行在run()函数所在的新线程中,而是和main函数在同一个线程中,即创建线程对象的线程。这有时恐怕不是我们想要的。所以,当需要线程中的槽函数完全在另一个新线程中执行时,就需要使用moveToThread()的方法。

    ---------------------
    作者:求道玉
    来源:CSDN
    原文:https://blog.csdn.net/Amnes1a/article/details/70171519
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    android 之 ListView 里面嵌套 GridView 遇到的问题及其解决方法。
    Android ViewPager使用详解
    android背景选择器selector用法汇总
    GSON
    Android View事件机制 21问21答
    Android Drawable的9种子类 介绍
    Android tab导航的几种方法:ActionBar tab +fragment,Viewpager+pagerTitleStrip,开源框架ViewPageIndicator 和 ViewPager
    提取页面共性用抽象类来写页面模板
    对SharePreference的封装
    Android 主题和选择器
  • 原文地址:https://www.cnblogs.com/findumars/p/10247608.html
Copyright © 2011-2022 走看看