wordpress改二级域名百度seo免费推广教程
目录
1、QThread常用API
2、Qt线程安全
3、使用线程QThread
4、connect函数的第五个参数
5、Qt互斥锁
5.1 QMutexLocker
6、条件变量
7、信号量
结语
前言:
线程是应用程序开发非常重要的概念,在Qt中,用QThread类来实现多线程,将线程有关的各种函数都封装到这个类中(包括线程执行函数),方便通过该类对线程进行控制。若要创建一个线程执行某些任务,则需要自定义一个继承QThread的类,重写线程执行函数让该线程执行我们期望的任务。
1、QThread常用API
使用QThread创建一个线程对象后,需要调用相关API来操作线程,比如启动线程,等待线程等操作以及获取线程信息,这些成员函数的介绍如下:
run() | 线程的执行函数,即线程所执行的任务,通过start函数间接调用他 |
start() | 真正意义上的创建线程,调用此函数才能让线程执行run函数的内容 |
currentThread() | 返回⼀个指向管理当前执行线程的QThread对象的指针 |
isRunning() | 如果线程正在运行返回true,否则返回false |
sleep() / msleep() / usleep() | 使线程休眠,单位为秒 / 毫秒 / 微秒 |
wait() | 线程等待,调用此函数的线程会阻塞在此函数处,若线程的run函数调用结束或者线程没有被启动,wait返回true。如果等待超时,此函数将返回false |
terminate() | 终止线程的执行 |
finished() | 当线程结束时会发出该信号 |
2、Qt线程安全
线程安全指的是多个线程修改共享资源时会发生意料之外的错误,而Qt中的共享资源毫无疑问是界面本身,而对界面的修改默认是在主线程,如果我们新建线程并对界面进行修改就会引发线程安全问题,因此Qt中严格规定新建的线程不能对界面进行修改!
若需要使用线程对界面进行修改,则采用信号与槽的方式,线程向主线程发送信号,主线程连接该信号与槽,在槽函数中进行对界面的修改,因为槽函数的执行是在主线程,因此不会出现线程安全问题。如下图:
3、使用线程QThread
用QLabel模拟一个时间表,首先在界面上创建一个按钮(pushbutton)和一个标签(label),按钮的作用是让线程开始执行任务,并将任务的结果打印到标签中,至此模拟出时间表。界面设计如下:
1、首先要实现上述功能,必须先创建一个继承自QThread的类,点击新建项目选择新建类:
2、然后自定义类名,并让该类继承QThread,如下:
3、创建完毕后会自动生成相关的文件和代码,我们需要做的是重写run函数,让该线程能够执行我们期望的任务,并且还要给该类自定义一个信号,目的是通过发送信号间接修改界面,mythread.h文件代码如下:
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QWidget>
#include <QThread>class mythread : public QThread
{Q_OBJECT
public:mythread();void run();//重写run函数signals:void mythread_signal(QString time);//自定义的信号
};#endif // MYTHREAD_H
mythread.cpp文件代码如下:
#include "mythread.h"
#include <QTime>
#include <QDebug>mythread::mythread()
{}void mythread::run()
{while(1){QString time = QTime::currentTime().toString("hh:mm:ss");qDebug()<<time;emit mythread_signal(time);//将时间传过去sleep(1);//每过一秒更新一次}}
4、上述线程的工作已经完成,现在需要在主线程中实例化线程对象,并在主线程中调用线程的start函数让线程跑起来,可以让按钮完成启动线程的工作,并且实现线程信号对应的槽函数,在该槽函数中进行对label的文本设置,widget.h文件代码如下:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <mythread.h>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();void thread_slot(QString time);//线程信号的槽函数private:mythread thread;//要想启动线程,必须创建线程对象Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(&thread,&mythread::mythread_signal,this,&Widget::thread_slot);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{thread.start();//启动线程
}void Widget::thread_slot(QString time)
{ui->label->setText(time);//设置文本
}
运行结果:
至此实现了使用线程将label标签设置为时间表的功能。整个程序的逻辑:点击按钮启动线程的start函数->线程开始执行run函数并不断的发送信号->主线程收到线程的信号后执行对应的槽函数实现label文本的更新。
4、connect函数的第五个参数
connect函数是用于连接信号与槽的,connect函数第五个参数为Qt::ConnectionType,用于指定信号与槽的连接类型,主要影响槽函数的执行逻辑,一般在多线程的情况下才会用到第五个参数。Qt::ConnectionType提供了以下五种方式:
Qt::AutoConnection | 在Qt中,会根据信号和槽函数是否处于统一线程自动选择连接类型。如果信号和槽函数在同⼀线程中,就会Qt:DirectConnection类型;如果它们位于不同的线程中,则使用Qt::QueuedConnection类型。 |
Qt::DirectConnection | 当信号发出时,槽函数会立即在同⼀线程中执行,可以理解为就是简单的一次函数调用。 |
Qt::QueuedConnection | 当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下⼀次事件循环时执行。 |
Qt::BlockingQueuedConnection | 与Qt:QueuedConnection类似,但是发送信号的线程会被阻塞,直到槽函数执行完毕。 |
Qt::UniqueConnection | 这是⼀个标志,可以使用位运算和上述任何⼀种连接类型组合使用。 |
5、Qt互斥锁
讲到多线程就自然离不开互斥锁的使用,由于多线程很容易导致线程安全问题,因此使用互斥锁限制多个线程对共享资源的访问。在Qt中,互斥锁主要是通过QMutex类来实现。互斥锁的使用如下。
1、首先创建一个继承QThread的类thread,并在该类中创建一把锁和一个静态变量,thread.h文件代码如下:
#ifndef THREAD_H
#define THREAD_H#include <QWidget>
#include <QThread>
#include <QMutex>class mythread : public QThread
{Q_OBJECT
public:mythread();void run();static int num;//让多个线程对该值进行++
private:static QMutex mutex;//让线程看到同一把锁
};#endif // THREAD_H
2、定义run函数,thread.cpp代码如下:
#include "thread.h"int mythread::num = 0;
QMutex mythread::mutex;mythread::mythread()
{}void mythread::run()
{for(int i = 0;i<100000;i++){mutex.lock();num++;mutex.unlock();}
}
3、在widget.cpp中创建两个线程,这两个线程同时对num进行++操作,最终观察num的值,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);mythread t1;mythread t2;t1.start();t2.start();t1.wait();t2.wait();qDebug()<<mythread::num;
}Widget::~Widget()
{delete ui;
}
运行结果:
此时结果是预期的,因为对访问共享资源进行加锁限制,如果上述代码没有加锁,结果如下:
从这里也可以看到加锁的必要性。
5.1 QMutexLocker
QMutexLocker是QMutex的辅助类,使用RAII(Resource Acquisition Is Initialization)方式 对互斥锁进行自动上锁和解锁的操作。具体来说,他会在被创建时自动上锁,在作用域结束后自动释放锁,避免开发者忘记解锁导致的死锁等问题。
将上述代码的上锁解锁操作用QMutexLocker代替:
#include "thread.h"int mythread::num = 0;
QMutex mythread::mutex;mythread::mythread()
{}void mythread::run()
{for(int i = 0;i<100000;i++){QMutexLocker locker(&mutex);//创建时自动加锁//mutex.lock();num++;//mutex.unlock();}//出了作用域后自动结束
}
运行结果:
6、条件变量
在Qt中,QWaitCondition类表示条件变量,条件变量是作用是让线程实现同步机制,线程同步是为了让所有线程申请到锁的能力是一样的,上述例子中,线程t1和线程t2虽然实现了互斥,但是不具备同步机制,可以通过观察执行线程的地址来判断申请锁的能力,如下图:
添加条件变量后,节选widget.cpp代码:
#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);mythread t1;mythread t2;t1.start();t2.start();for(int i = 0;i<1000;i++){_sleep(1);mythread::cond.wakeAll();//唤醒条件变量}t1.wait();t2.wait();qDebug()<<mythread::num;}Widget::~Widget()
{delete ui;
}
测试结果:
可以发现两个线程依次有序的对共享资源进行++操作。
7、信号量
信号量类似于加强版互斥锁,不仅能完成上锁和解锁操作,而且还可以限制访问共享资源的线程数量。Qt中用QSemaphore类来表示信号量,用于控制同时访问共享资源的线程数量,测试代码如下:
#include "thread.h"
#include <QDebug>
#include <QSemaphore>int mythread::num = 0;
QMutex mythread::mutex;
QWaitCondition mythread::cond;//初始化条件变量QSemaphore QS(1);//信号量为1,表示只能有一个线程申请到信号量mythread::mythread()
{}void mythread::run()
{for(int i = 0;i<1000;i++){QS.acquire();//尝试获取信号量,若已满则阻塞//QMutexLocker locker(&mutex);//cond.wait(&mutex);//条件变量等待//mutex.lock();num++;qDebug()<<this;//mutex.unlock();QS.release();//释放信号量}
}
结语
以上就是关于Qt线程介绍与使用,在Qt中使用线程最为重要的就是不能直接在线程中对界面进行修改,并且Qt线程采用的是继承的思想,和C++中的线程库采用回调的方式有些不同。线程的其他相关概念都相差无几。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!