QThread使用

QThread目前有两种使用方法
1、继承QThread类,重写run方法
2、使用movetoThread
接下来大概讲讲其使用。

  • 继承QThread方式
    这种方式比较原始,也相对来说比较简单,继承后重写一下run函数,再启动线程就好了,废话不多说了,看下实例代码:
class myThread : public QThread
{
    Q_OBJECT
public:
    explicit myThread(QObject *parent = nullptr);

signals:
    void runFinish();

protected:
    void run() override{
        static int times = 10;
        while(times--)
        {
            qDebug() << "mythread run: id = " << QThread::currentThreadId();
            QThread::msleep(500);
        }
        emit runFinish();
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    
private slots:
    void handleResult(){
        qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
    }

private:
    Ui::MainWindow *ui;
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myThread *thread = new myThread(this);
    connect(thread, &myThread::runFinish, this, &MainWindow::handleResult);
    connect(thread, &myThread::finished, thread, &QObject::deleteLater);
    thread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

运行看一下结果
在这里插入图片描述
这样一个线程就创建完毕了,我们可以在run函数里面实现我们想要的操作。我们如果想暂停或者继续的话一般会采用标志位的方式在while里面去进行处理,如下:

class myThread : public QThread
{
    Q_OBJECT
public:
    explicit myThread(QObject *parent = nullptr);

public slots:
    void runstart(){
        m_stop = false;
        qDebug() << "start thread id = " << QThread::currentThreadId();
    }
    void runstop(){
        m_stop = true;
        qDebug() << "stop thread id = " << QThread::currentThreadId();
    }

signals:
    void runFinish();

protected:
    void run() override{
        int times = 10;
        while(true)
        {
            while((!m_stop) && times)
            {
                times--;
                qDebug() << "mythread run: id = " << QThread::currentThreadId();
                QThread::msleep(500);
            }
            if(!times)
            {
                emit runFinish();
                break;
            }
        }
    }
private:
    bool m_stop = true;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    
private slots:
    void handleResult(){
        qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
    }

private:
    Ui::MainWindow *ui;
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "\nmain thread: id = " << QThread::currentThreadId();

    myThread *thread = new myThread(this);
    connect(thread, &myThread::runFinish, this, &MainWindow::handleResult);
    connect(thread, &myThread::finished, thread, &QObject::deleteLater);
    connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart);
    connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop);
    thread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

运行并查看输出信息:
在这里插入图片描述
可以看到我们点击start跟stop按钮可以控制run里面函数是否执行。这个时候我们观察线程id号可以看到,在我们触发信号后,执行槽函数的线程是我们的主线程,也就是我们发射信号的那个线程。说到这有些小伙伴已经发现了一个问题,那就在槽函数中我们如果与run函数中访问同一数据时容易发生冲突,这里需要一个锁来进行同步。那有没有好的办法不需要使用锁去处理呢?使用movetoThread能够解决这一问题。

  • 使用movetoThread方式
    修改代码如下:
class Myworker : public QObject
{
    Q_OBJECT
public:
    explicit Myworker(QObject *parent = nullptr){}

public:
    void setThreadStatus(bool status){
        qDebug() << "slot thread id: " << QObject::thread()->currentThreadId();
    }

public slots:
    void doWork(){
        int times = 10;
        qDebug() << "worker thread id: " << QObject::thread()->currentThreadId();
        while(times--)
        {
            static int count = 0;
            qDebug() << "worker is working" << "  " << count++;
            QThread::msleep(200);
        }
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void handleResult(){
        qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
    }

private:
    Ui::MainWindow *ui;
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "\nmain thread: id = " << QThread::currentThreadId();

    Myworker *worker = new Myworker;
    QThread *thread = new QThread(this);

    worker->moveToThread(thread);

    QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork);
    QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
        worker->setThreadStatus(true);
    });
    thread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

运行结果如下:
在这里插入图片描述
可以看到主线程id和执行槽函数线程id不一致,槽函数是由子线程执行的。这样主线程有时候可以通过信号槽的方式让子线程去执行比较耗时的操作,就不会造成主线程的卡顿了。其中connect的第五个参数Qt::ConnectionType会对结果造成影响,在QT信号与槽一文最后部分有对应参数的描述。单纯的文字描述也难以理解,借助QThread来说说我对这几个参数,Qt::AutoConnection,Qt::DirectConnection,Qt::QueuedConnection的理解。

在这里插入图片描述
我们在使用connect连接信号与槽时默认的参数就是这个,在第一种方式中创建的myThread对象依附于主线程,因此实际上接受者和发送者同在一个线程内,故实际默认使用的是Qt::AutoConnection这个参数。我们可以将

connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart);
connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop);

这部分改为

connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart, Qt::DirectConnection);
connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop, Qt::DirectConnection);

进行测试,实际上效果是一致的。将其替换为Qt::QueuedConnection后效果还是一样,因为他们在同一线程。我们再来看看Qt::DirectConnection描述:
在这里插入图片描述
也就是说槽函数一定是在信号发送那个线程执行。我们可以更改movetoThread方式创建的线程去测试,将连接信号槽部分进行修改如下:

QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork, Qt::DirectConnection);
QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
        worker->setThreadStatus(true);
		}, Qt::DirectConnection);

运行如下:
在这里插入图片描述
可以看到不管是槽函数还是那个worker线程都是主线程执行的,因为采用的是Qt::DirectConnection的模式,我们可以再修改测试一下,第一个connect不设置第五个参数(默认是Qt::AutoConnection)如下:

QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork);
QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
        worker->setThreadStatus(true);
		}, Qt::DirectConnection);

运行结果如下
在这里插入图片描述
可以看到在触发stop信号后主线程立马进入执行了槽函数,因此也验证了我们的想法。上述方式实际pushButton_start按下发送信号后采用Qt::QueuedConnection方式进行连接,其描述如下
在这里插入图片描述
从运行的结果中也可以看出,当控制权回到接受者所依附线程的事件循环时,槽函数被调用。通俗点就是执行完dowork函数才会执行对应槽函数。

与主线程通讯

子线程与主线程通讯的一个解决方案是借助信号-槽的连接。这里将看到的一种使用信号-槽方式主次线程通讯并且处理耗时操作不导致主线程卡顿的方法记录一下。代码如下:

#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
#include <QDebug>

class baseTask
{
public:
    virtual int doWork(const int value) = 0;
    virtual QString message() = 0;
};

class addNumber : public baseTask
{
    int doWork(const int value){
        QThread::msleep(5000);
        return 10 + value;
    }
    QString message(){
        return QString("Add Number");
    }
};

class subNumber : public baseTask
{
    int doWork(const int value){
        QThread::msleep(5000);
        return value - 10;
    }
    QString message(){
        return QString("sub Number");
    }
};

class mulNumber : public baseTask
{
    int doWork(const int value){
        QThread::msleep(5000);
        return value * 10;
    }
    QString message(){
        return QString("mul Number");
    }
};

class taskThread : public QThread
{
    Q_OBJECT

public:
    taskThread(){}
    ~taskThread()
    {
        {
            QMutexLocker locker(&mutex);
            while(!tasks.isEmpty()){
                delete tasks.dequeue();
            }
            tasks.enqueue(nullptr);
            taskWait.wakeOne();
        }
        wait();
    }
public:
    void addTask(baseTask *task){
        QMutexLocker locker(&mutex);
        tasks.enqueue(task);
        taskWait.wakeOne();
    }
    void setValue(int value){
        QMutexLocker locker(&mutex);
        currentValue = value;
    }
    int getValue(){
        QMutexLocker locker(&mutex);
        return result;
    }

protected:
    void run() override
    {
        baseTask *task = nullptr;
        int value;
        while(1)
        {
            {
                QMutexLocker locker(&mutex);
                if(tasks.isEmpty())
                    taskWait.wait(&mutex);
                qDebug() << "$$$$$$$$$$$$$$$$$$";
                task = tasks.dequeue();
                if(!task) break;//说明需要退出线程
                value = currentValue;
            }
            emit taskStarted(task->message());//发送正在处理的信息
            qDebug() << currentValue;
            result = task->doWork(value);
            delete task;
            if(tasks.isEmpty())
                emit tasksFinish();
        }
    }

signals:
    void taskStarted(const QString &message);
    void tasksFinish();

private:
    int result;
    int currentValue;
    QQueue<baseTask *> tasks;
    QWaitCondition taskWait;
    QMutex mutex;
};

class myWidget : public QWidget
{
    Q_OBJECT

public:
    explicit myWidget(QWidget *parent = 0) : QWidget(parent)
    {
        thread.start();
        thread.setValue(20);
        label = new QLabel("ready", this);
        label->move(100, 100);
        label->resize(200, 200);
        add = new QPushButton("add", this);
        sub = new QPushButton("sub", this);
        mul = new QPushButton("mul", this);
        test = new QPushButton("test", this);
        add->move(0, 0);
        sub->move(100, 0);
        mul->move(200, 0);
        test->move(300, 0);
        connect(add, SIGNAL(clicked(bool)), this, SLOT(on_add_clicked(bool)));
        connect(sub, SIGNAL(clicked(bool)), this, SLOT(on_sub_clicked(bool)));
        connect(mul, SIGNAL(clicked(bool)), this, SLOT(on_mul_clicked(bool)));

        connect(&thread, SIGNAL(taskStarted(QString)), this, SLOT(showMessage(QString)));
        connect(&thread, SIGNAL(tasksFinish()), this, SLOT(doFinish()));
    }
    ~myWidget(){}

private:
    void addTask(baseTask *task){
        thread.addTask(task);
        add->setEnabled(false);
        sub->setEnabled(false);
        mul->setEnabled(false);
    }

private slots:
    void showMessage(QString message){
        label->setText(message);
    }
    void doFinish(){
        label->setText(QString("result = %1").arg(thread.getValue()));
        add->setEnabled(true);
        sub->setEnabled(true);
        mul->setEnabled(true);
    }
    void on_add_clicked(bool){
        addTask(new addNumber);
    }
    void on_sub_clicked(bool){
        addTask(new subNumber);
    }
    void on_mul_clicked(bool){
        addTask(new mulNumber);
    }

private:
    taskThread thread;
    QLabel *label;
    QPushButton *add;
    QPushButton *sub;
    QPushButton *mul;
    QPushButton *test;
};

这里我们假设add、sub、mul三个都是比较耗时的操作,由于具有类似的特性,我们定义一个标准的基类baseTask,通过继承的方式让它们实例化。由于两个线程都存在对变量操作的情况,因此在taskThread中对相关的变量访问都需要加锁进行处理。程序运行后我们点击对应的add、sub、mul操作都是转到子线程中进行操作的,因此不会存在卡界面的问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

复杂的世界311

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值