计算机系统应用教程网站

网站首页 > 技术文章 正文

Qt几种多线程的实现 c++多线程实现的四种方式

btikc 2024-10-24 09:32:03 技术文章 7 ℃ 0 评论

Qt几种实现多线程的方式。如下:

  1. 继承QThread,重写run()函数
  2. 使用moveToThread将一个继承QObject的子类移至线程,内部槽函数均在线程中执行
  3. 使用QThreadPool,搭配QRunnable(线程池)
  4. 使用QtConcurrent(线程池)

1、使用类QThread

代码完全在一个独立的线程中运行,需要继承QThread类,并且重写该类的run()方法。实现方式比较简单,线程在执行完run()函数之后退出。

class WorkerThread : public QThread
{
    Q_OBJECT
protected:
    void run() Q_DECL_OVERRIDE {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

private:
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();
}

优点:

  • 可以使用信号槽进行通信

缺点:

  • 实例化的子类是在创建线程的旧线程中,不是在新创建的子线程中,因此,与该线程相关所有槽队列(如果有的话),都会在创建的旧线程中执行。不能直接在新建的线程中使用槽,如果需要,则需要借助worker - object实现。
  • 每创建一个线程都需要继承QThread,实现一个新的子类,使用不便。
  • 需要自己管理资源,线程的创建和释放,都需要自己手动管理,并且,频繁的创建和删除会造成比较大的内存开销。
  • 实例化子类的构造函数和run()函数在不同的线程中运行,因此,假设有成员变量两个函数中都能访问,则需要注意,多线程中资源的访问问题。

适用场景:

  • 线程不会被频繁的创建和删除,常驻内存的线程。

【文章福利】Qt开发学习资料包、大厂面试题、技术视频和学习路线图,包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、QML、Opencv、qt线程等等)有需要的可以进企鹅裙937552610领取哦~

2、使用QThread类moveToThread方法

创建一个继承QObject的类(MyObject),并把创建的MyObject类通过方法 movetothread 到创建好的子线程中,然后start子线程,这样就实现了一个子线程。主线程通过发送信号,调用 MyObject 中的方法,从而实现在子线程中的计算。不需要继承QThread,不需要重写run()函数。

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

需要注意的点:

  • 只有在槽中执行的操作才是在线程中执行的,所以需要通过连接信号槽的方式来实现
  • 如果object对象存在父对象,不能将其移到子线程中执行
  • 相对来说比较方便,适用于一些比较复杂的业务中

3、QThreadPool与QRunnable

QRunnable 是所有可执行对象的基类。 QRunnable类是一个接口, 用于表示需要执行的任务或代码段, 具体任务在run() 函数内部实现。实现的过程跟QThread类似,也是需要通过继承来实现。

可以通过搭配QThreadPool,使得其在单独的线程中执行代码。如果autoDelete()返回true(默认值),QThreadPool将自动删除QRunnable。使用setAutoDelete()来更改自动删除标记。

QThreadPool支持通过在run()函数中调用QThreadPool::tryStart(this)来多次执行同一个QRunnable。如果autoDelete被启用,QRunnable将在最后一个线程退出run函数时被删除。

如果启用了autoDelete,使用相同的QRunnable多次调用QThreadPool::start()会造成多线程访问同一资源,形成竞争,因此不推荐使用。

class Runnable : public QRunnable
{
    //Q_OBJECT		QRunnable 不是QObject的子类,因此在这儿需要注意
public:

    ~Runnable()
    {
        qDebug() << "~Runnable..."  << endl;
    }
    void run()
    {
        qDebug() << " Runnable run thread id :" << QThread::currentThreadId() << endl;
        //...
    }
};

void MyObject::startWorkInRunable()
{
    qDebug() << "main thread id :" << QThread::currentThreadId() << endl;
    Runnable run;
    QThreadPool::globalInstance()->start(&run);
}

优点:

  • 不用资源管理,QThreadPool 启动线程执行完成后会自动释放

缺点:

  • 可能会形成多线程资源竞争
  • 不能使用信号槽(信号槽只能在QObject中使用)

适用场景:

  • QRunnable适用于线程任务量比较大,需要频繁创建线程。QRunnable能有效减少内存开销。


4、使用QtConcurrent

Concurrent是并发的意思,QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展。

QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。

QFuture<T> run(Function function, ...)
QFuture<T> run(QThreadPool *pool, Function function, ...)   //Qt5.4引入
//T与函数的返回值类型相同。非void类型返回值可以通过QFuture::result()函数访问。

#include <QtConcurrent/QtConCurrent>
#include <QFuture>

QFuture<void> func = QtConcurrent::run(this, &MyClass::measure, param);

void MyClass::measure(const QString& param)
{
	//do something...
}

调用非常简单,这样我们设置的函数会在一个单独的线程中执行,这个线程是从QThreadPool池中获取的,因此,该线程可能不会直接执行(如果QThreadPool池中没有空闲的线程),当QThreadPool池中有空闲的线程后会执行,执行完成后会将线程还给QThreadPool。

QtConcurrent::run()返回的QFuture不支持取消、暂停或进度报告。返回的QFuture只能用于查询函数的运行/完成状态和返回值。非void类型返回值可使用QFuture::result()访问获取。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表