目录
概述
QThread常用API
线程使用
创建一个QThread的子类
主线程启动线程
线程安全
互斥锁
QMutex
使用示例:两个线程一个共享静态变量进行++
线程子类创建
主线程调用
QMutexLocker
条件变量
信号量
概述
- 在 Qt 中,多线程的处理⼀般是通过 QThread类 来实现。
- QThread 代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据。
- QThread 对象管理程序中的⼀个控制线程。
QThread要想创建线程,就需要创建出这样类的实例,创建线程的时候,需要重点指定线程的入口函数,创建一个QThread的子类,重写其中的run函数,起到指定入口函数的方式。
QThread常用API
run() | 线程的⼊⼝函数.. |
start() | 通过调⽤ run() 开始执⾏线程。操作系统将根据优先级参数调度线程。如果线程已经在运⾏,这个函数什么也不做。 |
currentThread() | 返回⼀个指向管理当前执⾏线程的 QThread的指针。 |
isRunning() | 如果线程正在运⾏则返回true;否则返回false。 |
sleep() / msleep() / usleep() | 使线程休眠,单位为秒 / 毫秒 / 微秒 |
wait() | 阻塞线程,直到满⾜以下任何⼀个条件: 与此 QThread 对象关联的线程已经完成执⾏(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了⼏毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。这提供了与 POSIX pthread_join() 函数类似的功能 |
terminate() | 终⽌线程的执⾏。线程可以⽴即终⽌,也可以不⽴即终⽌,这取决于操作系统的调度策略。在terminate() 之后使⽤ QThread::wait() 来确保。 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作。 |
线程使用
由于存在线程安全的问题,多个线程同时对于界面的状态进行修改,此时就会导致界面出错了,因此在qt中,多线程内部不允许对界面的控件状态进行修改,只能在主线程中进行修改。
创建一个QThread的子类
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QWidget>class Thread : public QThread
{Q_OBJECT
public:Thread();void run();
signals:void notify();
};#endif // THREAD_H
thread.cpp
#include "thread.h"Thread::Thread()
{}void Thread::run()
{for(int i = 0 ; i < 10; i++){sleep(1);emit notify();}
}
主线程启动线程
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "thread.h"
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void handle();
private:Ui::Widget *ui;Thread thread;
};
#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,&Thread::notify,this,&Widget::handle);thread.start();
}Widget::~Widget()
{delete ui;
}void Widget::handle()
{int value = ui->lcdNumber->intValue();ui->lcdNumber->display(--value);
}
客户端中的多线程,主要用于通过多线程的方式,执行一些耗时的等待IO的操作,避免主线程被卡死,比如当用户需要上传/下载一个很大的文件,我们可以使用单独的线程,来处理这种密集的IO操作,要挂起也是挂起这个新的线程,主线程负责事件循环,负责处理用户的各种操作。
线程安全
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
互斥锁
互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过 QMutex类来处理。QMutex中lock加锁,unlock解锁。
QMutex
使用示例:两个线程一个共享静态变量进行++
线程子类创建
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QWidget>
#include <QMutex>
class Thread : public QThread
{Q_OBJECT
public:Thread();void run();//共享资源static int num;//创建一把锁QMutex mutex;
signals:void notify();
};#endif // THREAD_H
thread.cpp
#include "thread.h"int Thread::num=0;
Thread::Thread()
{}void Thread::run()
{for(int i = 0 ; i < 10; i++){mutex.lock();num++;mutex.unlock();}
}
主线程调用
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);Thread::num=0;Thread t1,t2;t1.start();t2.start();//让主线程等待两个线程执行完毕t1.wait();t2.wait();qDebug()<<Thread::num;
}Widget::~Widget()
{delete ui;
}
QMutexLocker
上述的代码中,在获取到锁后,执行我们指定的逻辑,一旦发生错误,或者异常将会导致锁未释放,因此我们需要引入 QMutexLocker locker(&mutex);
特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII(Resource Acquisition Is Initialization)⽅式 对互斥锁进⾏上锁和解锁操作。⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
代码示例:
void Thread::run()
{for(int i = 0 ; i < 10; i++){QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁num++;}//在作⽤域结束时⾃动解锁
}
此外还有QReadWriteLocker、QReadLocker、QWriteLocker
特点:
- QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
- QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
- QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
- ⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提 供了更⾼效的并发访问⽅式。
条件变量
- 特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
- ⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调
也就是说多个线程之间的调度是无序的,为了能够一定程度的干预线程之间的执行顺序,需要引入条件变量。
QMutex mutex;QWaitCondition condition;// 在等待线程中mutex. lock ();// 检查条件是否满⾜,若不满⾜则等待while (! conditionFullfilled ()){condition. wait (&mutex); // 等待条件满⾜并释放锁}// 条件满⾜后继续执⾏//...mutex. unlock ();// 在改变条件的线程中mutex. lock ();// 改变条件changeCondition ();condition. wakeAll (); // 唤醒等待的线程mutex. unlock ();
信号量
- 特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
- ⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。
QSemaphore semaphore ( 2 ); // 同时允许两个线程访问共享资源// 在需要访问共享资源的线程中semaphore. acquire (); // 尝试获取信号量,若已满则阻塞// 访问共享资源//...semaphore. release (); // 释放信号量// 在另⼀个线程中进⾏类似操作