QThread多线程编程分析

155 篇文章 26 订阅

QThread多线程编程分析

传统图形界面应用程序都只有一个线程执行,并且一次执行一个操作。如果用户调用一个比较耗时的操作,就会冻结界面响应。一个解决方法是按照事件处理的思路:调用 Void QApplication::processEvents()  void QApplication::processEvents ( int maxtime ) 来强迫事件循环进行,但是这种做法是有潜在风险的。

按照QCoreApplication:processEvents()可能会引起递归,导致栈溢出崩溃的说法,当主线程在某个槽函数里正在执行processEvents时,刚好有一个能响应此槽函数的信号发送过(肯定是其他线程发的信号),这时就可能会发生可怕的递归,导致栈溢出崩溃。原因是processEvents在处理自己槽函数的事件时,又会调用到processEvents,进入到无尽的递归中。

另外一个解决方法是:采用多线程。

QT QThread多线程编程的方法一直有个争议,就是Bradley T. HughesYoure doing it wrong归纳为3中方法优劣问题:

方法1:

不使用事件循环。这是官方的 Manual example 以及相关书籍中都介绍的一种的方法。

  1. 子类化 QThread
  2. 重载 run 函数,run函数内有一个 while for 的死循环;
  3. 设置一个标记为来控制死循环的退出。

方法2:

这种方法也是Bradley T. Hughes极力批判的。

  1. 子类化 QThread
  2. 重载 run 使其调用 QThread::exec()
  3. 并为该类定义信号和槽,这样一来,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用 moveToThread(this);而争论和不解正是这样的一条语句造成的。

Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

方法3:

Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。而从Qt4.4开始,qthreads-no-longer-abstract run 默认调用 QThread::exec() 。这样一来不需要子类化 QThread 了,只需要子类化一个 QObject 就够了,这正是被 Bradley T. Hughes推荐的方法。

类似于:

QThread thread;

Object obj;

Dummy dummy;

obj.moveToThread(&thread);

QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));

thread.start();

回到问题的本质,我们想要新开一个子线程来完成耗时的操作,有两个地方可以实现:

1)子类化QThreadrun()函数里,在run()函数里执行耗时的操作。因为run()函数是子线程的入口函数,一定不和主线程在同一个线程。就是方法1介绍的。如果在QThread子类里的slot里执行耗时操作,因为slot是在主线程中执行的,所以行不通。

2)利用方法(3),子类化QObject ,object。在objectslot里执行耗时操作。但是slot仍在主线程里执行,怎么办?QThread *threadobject.moveToThread(thread); 这样,这个QObjectslot都会在子线程里执行,达到了和主线程区分开的目的啦。

所以,方法(1)和方法(3)是正确的QT QThread多线程编程方法,方法(2)用的最多,但只是三人成虎,一种不恰当的用法。下面给出方法(1)和方法(3)的代码片段,各种思路请慢慢斟酌。

方法1例程:

主线程开两个子线程,子线程A打印A,子线程B打印B,通过一个dialog控制。

(1)thread.h:

 

#ifndef THREAD_H

#define THREAD_H

 

#include <QThread>

 

class Thread : public QThread

{

    Q_OBJECT

 

public:

    Thread();

 

    void setMessage(const QString &message);

    void stop();

 

protected:

    void run();

 

private:

    QString messageStr;

    volatile bool stopped;

};

 

#endif

(2)threaddialog.h:

#ifndef THREADDIALOG_H

#define THREADDIALOG_H

 

#include <QDialog>

 

#include "thread.h"

 

class QPushButton;

 

class ThreadDialog : public QDialog

{

    Q_OBJECT

 

public:

    ThreadDialog(QWidget *parent = 0);

 

protected:

    void closeEvent(QCloseEvent *event);

 

private slots:

    void startOrStopThreadA();

    void startOrStopThreadB();

 

private:

    Thread threadA;

    Thread threadB;

    QPushButton *threadAButton;

    QPushButton *threadBButton;

    QPushButton *quitButton;

};

 

#endif

(3)thread.cpp

#include <QtCore>

#include <iostream>

 

#include "thread.h"

 

Thread::Thread()

{

    stopped = false;

}

 

void Thread::setMessage(const QString &message)

{

    messageStr = message;

}

 

void Thread::run()

{

    while (!stopped)

        std::cerr << qPrintable(messageStr);

    stopped = false;

    std::cerr << std::endl;

}

 

void Thread::stop()

{

    stopped = true;

}

(4)threaddialog.cpp

#include <QtGui>

 

#include "threaddialog.h"

 

ThreadDialog::ThreadDialog(QWidget *parent)

    : QDialog(parent)

{

    threadA.setMessage("A");

    threadB.setMessage("B");

 

    threadAButton = new QPushButton(tr("Start A"));

    threadBButton = new QPushButton(tr("Start B"));

    quitButton = new QPushButton(tr("Quit"));

    quitButton->setDefault(true);

 

    connect(threadAButton, SIGNAL(clicked()),

            this, SLOT(startOrStopThreadA()));

    connect(threadBButton, SIGNAL(clicked()),

            this, SLOT(startOrStopThreadB()));

    connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

 

    QHBoxLayout *mainLayout = new QHBoxLayout;

    mainLayout->addWidget(threadAButton);

    mainLayout->addWidget(threadBButton);

    mainLayout->addWidget(quitButton);

    setLayout(mainLayout);

 

    setWindowTitle(tr("Threads"));

}

 

void ThreadDialog::startOrStopThreadA()

{

    if (threadA.isRunning()) {

        threadA.stop();

        threadAButton->setText(tr("Start A"));

    } else {

        threadA.start();

        threadAButton->setText(tr("Stop A"));

    }

}

 

void ThreadDialog::startOrStopThreadB()

{

    if (threadB.isRunning()) {

        threadB.stop();

        threadBButton->setText(tr("Start B"));

    } else {

        threadB.start();

        threadBButton->setText(tr("Stop B"));

    }

}

 

void ThreadDialog::closeEvent(QCloseEvent *event)

{

    threadA.stop();

    threadB.stop();

    threadA.wait();

    threadB.wait();

    event->accept();

}

(5)main()

#include <QApplication>

 

#include "threaddialog.h"

 

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

{

    QApplication app(argc, argv);

    ThreadDialog dialog;

    dialog.show();

    return app.exec();

}

测试结果:

https://i-blog.csdnimg.cn/blog_migrate/044e55be42078fd530e9817ee533c232.png

https://i-blog.csdnimg.cn/blog_migrate/0214cf4dc7eb07ae7f26a793e1617dbc.png

方法3例程:

#include <QtCore/QCoreApplication>

#include <QtCore/QObject>

#include <QtCore/QThread>

#include <QtCore/QDebug>

 

class Dummy:public QObject

{

Q_OBJECT

public:

Dummy(QObject* parent=0):QObject(parent) {}

public slots:

void emitsig()

{

emit sig();

}

signals:

void sig();

};

 

class Object:public QObject

{

Q_OBJECT

public:

Object(){}

public slots:

void slot()

{

qDebug()<<"from thread slot:" <<QThread::currentThreadId();

}

};

 

#include "main.moc"

 

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

{

QCoreApplication a(argc, argv);

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

QThread thread;

Object obj;

Dummy dummy;

obj.moveToThread(&thread);

QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));

thread.start();

dummy.emitsig();

return a.exec();

}

结果:slot确实不在主线程中运行。

main thread: 0x1a5c

from thread slot: 0x186c其他

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值