一、QThread类:为Qt中所有线程的基类。
- QThread常用方法
函数名 | 功能 |
---|---|
start() | 槽函数,调用该方法后,会发出started()信号,且之后会执行run()方法 |
run() | 为虚函数,子类派生于QThread会重写run()方法,该方法就是线程要执行的内容 |
wait() | 用于阻塞,知道线程结束才返回 |
setPriority() | 设置线程的优先级,注意优先级必须在run()方法里设置,因为main()中设置不了 |
isFinished() | 线程是否结束 |
isrunning() | 线程是否在运行 |
terminate() | 槽函数,强制终止线程,可以在线程的任何阶段被终止,所以可能导致线程资源无法清理。(不建议用该函数) |
- QThread的静态成员以及常用信号
静态方法 | 功能 |
---|---|
currentThreadId() | 获取当前的线程Id |
sleep() | 使得当前线程睡眠多少秒 |
msleep() | 使得当前线程睡眠多少毫秒 |
usleep() | 使得当前线程睡眠多少微秒 |
常用信号 | 功能 |
---|---|
started() | 线程开始后发出该信号。 |
finished() | 线程执行结束后发出此线号。 |
创建线程的常见方式
方式一:创建一个子类继承于Qthread,并重写虚函数run()
class Mythread: public QThread{
Q_OBJECT
public:
MyThread(){ };
void run() override;
private:
}
void MyThread::run(){
setPriority(优先级的枚举); //如果设置优先级,就必须在run方法里设置优先级,
//main函数中设置不了,因为只有线程运行起来才能设置优先级。
qDebug()<< QThread::currentThreadId();
msleep(毫秒); //可以是CPU不那么忙。
}
int main(){
MyThread my;
my.start(); //开始启动线程;
my.wait();
}
方式二:创建一个子类,定义我们线程要执行的内容(这里可以定义多个函数)
class Worker : public object{
Q_OBJECT
public slots: //这两个槽函数,用于执行多线程的业务逻辑
void Do(){
}
void Work(){
}
}
int main(){
Worker * worker=new Worker(); //定义worker类对象,
QThread *t=new QThread(); //定义线程;
worker->moveToThread(t); //将worker加入到线程中
t->start(); //这行必须要,因为只有启动了t这个线程, worker收到信号才能反应
connect(this,信号A,w,&Worker::Do);//用信号A触发Do函数来执行多线程
connect(this,信号B,w,&Worker::Work); //用信号B触发Work函数来执行多线程
//Do和work是在同一个线程中
}
二、线程的互斥与互锁
1. QMutex类:一个线程互斥锁,多线程访问同一个资源时时,难免会出现冲突,互斥锁可以保证多个线程之间执行互斥
QMutex mm;//这个互斥锁对象必须是全局变量
void MyThread::run() {
while(ticktes>0){
mm.lock(); //加锁 ,使得同类线程对象A,B互斥,在这个所内A和B线程只能执行一个
QThread::msleep(1);
qDebug()<<QStringLiteral("线程")<<name<<QStringLiteral("卖票号:")<<ticktes;
ticktes;
mm.unlock(); //解锁,解锁后A,B线程不在互斥,等到下次加锁在互斥
}
}
QMutex互斥:lock和unlock之间的代码,可以看成是一个最小执行单位,如果A线程执行了这个最小单位,那么就必须等A执行完这个最小单位,其他同类对象的线程B才能执行这个最小单位。
2. QMutexLocker类:是一个更方便的互斥类,它需要结合QMutex搭配,用于自动锁定和互斥解锁,不需要手动解锁。
QMutex mutex;//mutex为全局变量
void MyThread::run(){
while(tickets>0){
QMutexLocker mlck(&mutex);
//·········
//·········
//·········
}
}
QMutexLocker互斥:QMutexLocker的互斥锁默认是它外面一层的作用域,如上的while循环的{ },就是作用域,在这个作用域自动加锁和解锁。
3. QRecursiveMutex: 是一个递归互斥锁类,继承与QMutex,它运行一个线程多次加锁和解锁,但是加锁和解锁次数保持一致。
不仅可以满足同类型线程的不同对象线程互斥,还能满足同一线程内可以多次加锁,解锁。(如:线程内的fun1(),fun2()加锁后,,那么fun2()调用fun1(),由于fun2()有锁,如果这个锁是QMutex类对象就会卡死,这 时候就可以使用QRecursiveMutex)/
QRecursiveMutex mm;//不会卡死,如果是QMutex mm,那么就会阻塞
void MyThread::run(){
qDebug()<<name<<"run被执行";
fun2();
qDebug()<<name<<"run结束";
}
void MyThread::fun1() {
mm.lock();
qDebug()<<"fun1被执行 " ;
mm.unlock();
}
void MyThread::fun2() {
mm.lock();
qDebug()<<"fun2被执行";
fun1();
mm.unlock();
}
QRecursiveMutex 与 QMutex的区别:QMutex:保证了线程间的互斥,但缺点是在 同一作用域内不能多次加锁和解锁。QRecursiveMutex: 可以实现不同线程互斥,还可以解决QMutex的缺点。
4. QSemaphore类: 可以理解为互斥量QMutex类的增强版,QSemaphore类互斥量只能获取一次,而信号资源可以获取多次,可以保护一定数量的同种资源
QSemaphore的常用方法
函数名 | 功能 |
---|---|
QSemaphore(int n) | 构造时,传入被访问的资源数量n ,可以允许n个资源同时被使用 |
availavle | 返回信号量当前可以用的资源数 |
acquire(int n) | 尝试获取n个资源。如果n>available(), 这个调用会阻塞,直到有足够的资源可用 |
release(int n) | 释放用信号两边保护的n个资源, |
举例子,就是有一个停车场有3个车位,外面有n辆车想要停在停车场,停车场一下子停不了n辆,最多只能停3辆,停车场里的车离开几辆,外面的车才能进来几辆。(停车场里的3个车位就比如多个允许的线程资源,外面的总线程)
extern QSemaphore sem(3); //全局变量中声明,表示允许3个线程同时访问资源。
void CarThread::run(){
while(true){
sem.acquire();// 减少一个线程资源数。 如果n>3,那么将会阻塞。
qDebug()<<name<<QStringLiteral("进入车位!");
QThread::run(); //停车时间;
qDebug()<<name<<QStringLiteral("离开车位!");
sem.release(); //释放一个车位;即在次增加一个线程资源数。
}
}
三、线程同步
QWaitCondition类:允许一个或多个线程可以阻塞 wait() 等待执行,由**WakeOne()或WakeAll()**去唤醒一个或多个线程。
应用场景模拟:现有一个仓库,可以容纳3个商品。我们建立3个生产者线程不断往仓库放商品,1个消费者线程慢慢从仓库里每次取一个商品。(供大于求)
解决方案:当仓库满了,调用QWaitCondition的wait()方法使得3个生产者线程全阻塞,直到消费者拿出一个商品,消费者发出WakeAll()方法,生产者才继续生产。
QWaitCondition goProducer; //用于阻塞线程,一般放在全局
QMutex mutex; // 用于线程间的互斥;
goProducer.wait(&mutex); //使得线程阻塞
goProducer.wakeAll();//唤醒所有之前在wai的线程
//goProducer.wakeOne();//随机唤醒一个阻塞的线程
四、线程池
QRunnable类:为一个可执行的类的抽象类(其中run()为纯虚函数),因此,需要派生类需要重写run()方法。
QThreadPool类:由于线程的创建和销毁需要很大的CPU开销。如果一个程序频繁的创建和销毁线程,那么将极大的影响CPU的性能。那就引出了线程池,将一定数量的线程放入池中进行统一管理,当需要进行多线程运算时将运算任务扔给线程池,线程池自动分配线程来处理。
线程池的优点:
1. 降低资源开销:线程池通过重复利用已有的线程,避免频繁创建和销毁线程所产生的开销。
2. 提高响应速度:当任务到达时,可以不需要等待线程创建,立即执行。
3. 通过线程的利用率: 使用线程池可以进行统一的分配,监控和调优。
QThreadPool类的常用方法
函数名 | 功能 |
---|---|
setMaxThreadCount | 设置线程池的最大线程数量 |
start | 加入一个任务(QRunnable的派生类)到线程池中并执行,如果线程池数量达到最大,则放入运行队列中等待执行 |
activeThreadCount | 返回线程池中的活跃线程个 |
waitForDone( int n ) | 等待线程池中所以线程任务执行完毕,未超时返回true,超时返回false。如果是 n=-1(默认值),永不超时 |
QThreadPool pool;
Class MyTask:public QRunnable
{
public:
MyTask(QString name){
this->name=name;
}
~MyTask(){
qDebug()<<name<<"任务结束";
}
void run(){
qDebug()<< name <<Stringliteral("开始执行任务")<<QThread::currentID();
}
private:
QString name;
}
void ZhixingYouxianji(){
for(int i=1;i<=10,i++){
MyTask* t=new MyTask(QStringliteral("任务").arg(i));
pool.start(t,i); //启动第i个线程,这个i越大,优先级就越高。但是有一点要注意,我们在下面有说明
}
//等待线程池中的所有任务都执行完毕才结束执行,否则就阻塞
int n=10;//
pool.waitForDone(n);//等待10s, 如果括号内传入-1,代表永远等待,界面就会卡住,
qDebug()<<"函数执行结束";
}
注意: start(t,i)
这个i越大,说明这个t线程优先级越高,但是有个前提,这个线程是在等待队列中。所以优先级是在队列中进行排列的。 如果一个空线程池,这些线程没有进入队列等待,那么线程的执行就按线程加入线程池的先后顺序来执行。
参考黄强老师的多线程编程