目录
一、QThread类
1.常用方法
2.信号
3.注意事项
二、多线程实现方式
1.继承QThread类并重写run函数
2.继承QObject类并使用moveToThread()
3.使用QRunnable和QThreadPool
4.使用QtConcurrent
三、线程同步
1.互斥锁(QMutex)
2.读写锁(QReadWriteLock)
3.条件变量(QWaitCondition)
4.信号量(QSemaphore)
5.便捷类
一、QThread类
QThread类是Qt中用于创建和管理线程的基础类,提供了跨平台的线程操作接口。
1.常用方法
- start():用于启动线程,调用后会执行run()函数,在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。
- run():线程的入口点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个函数,便于管理自己的线程。该方法返回时,该线程的执行将结束。
- quit():告诉线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。
- exit(int returnCode = 0):告诉线程事件循环退出。调用这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,0表示成功,任何非0值表示失败。
- terminate():终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait(),以确保万无一失。当线程被终止后,所有等待中的线程将会被唤醒。此函数比较危险,不鼓励使用。
- wait(unsigned long time = ULONG_MAX):线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。
- isFinished():判断线程是否结束。
- isRunning():判断线程是否正在运行。
- setPriority(Priority priority):设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。
- currentThreadId():返回标识当前正在执行的线程的平台特定的ID。
- currentThread():返回一个指向管理当前执行线程的QThread的指针。
- msleep(unsigned long msecs):强制当前线程睡眠msecs毫秒。
- sleep(unsigned long secs):强制当前线程睡眠secs秒。
- usleep(unsigned long usecs):强制当前线程睡眠usecs微秒。
2.信号
- finished():线程执行结束时发射此信号。
- started():线程启动时发射此信号。
- terminated():线程被终止时发射此信号。
3.注意事项
- 在使用QThread时,需要注意线程安全问题,尤其是在多个线程访问共享资源时,需要使用互斥锁等机制进行同步。
- 在主线程中创建的对象不能直接在子线程中使用,需要通过信号和槽等方式进行传递和操作。
- 尽量避免使用terminate()函数来终止线程,因为它可能导致资源泄漏和数据不一致等问题。
二、多线程实现方式
1.继承QThread类并重写run函数
①主要步骤:
- 创建线程类:自定义一个类继承自QThread类,在类中重写run函数,将需要在子线程中执行的代码放在run函数内。
- 启动线程:在主线程中创建自定义类的实例,然后调用start函数启动线程,此时run函数中的代码将在子线程中执行。
ps:只有run函数在子线程中执行。
②示例:
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = nullptr);
void run() override;
};
void WorkerThread::run()
{
qDebug()<<"WorkerThread:"<
qDebug()<<"主线程:"<start();
th->quit();
th->wait();
2.继承QObject类并使用moveToThread()
①主要步骤:
- 创建工作类:创建一个继承自QObject的类,在该类中定义需要在子线程中执行的槽函数。
- 创建线程和对象实例:在主线程中创建一个QThread实例和工作类的实例。
- 移动对象到线程:调用继承自MyObject类的moveToThread函数,将其移动到创建的QThread线程中。
- 启动线程并连接信号槽:启动QThread线程,然后通过信号槽机制连接信号和槽函数,以便在主线程中发送信号来执行子线程中的槽函数。
ps:调用moveToThread函数的对象不能设置父对象。
②示例:
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void work();
};
void Worker::work()
{
qDebug()<<"Worker:"<
qDebug()<<"主线程:"<moveToThread(th);
connect(th,&QThread::started,worker,&Worker::work);
th->start();
th->quit();
th->wait();
3.使用QRunnable和QThreadPool
①主要步骤:
- 创建一个继承自QRunnable的类,在该类中重写run函数,将需要在子线程中执行的代码放在run函数内。
- 在主线程中获取全局线程池实例QThreadPool::globalInstance(),然后创建MyRunnable类的实例。
- 调用MyRunnable类实例的setAutoDelete函数设置为自动删除,然后将其传递给线程池的start函数来启动线程,线程池会自动管理线程的执行和资源释放。
②示例:
class Task : public QObject, public QRunnable
{
Q_OBJECT
public:
Task(QObject *parent = nullptr);
void run() override;
};
Task::Task(QObject *parent)
: QObject{parent}
{
setAutoDelete(true); //设置为自动删除
}
void Task::run()
{
qDebug()<<"Task:"<
qDebug()<<"主线程:"<setMaxThreadCount(3);
Task *task = new Task;
QThreadPool::globalInstance()->start(task);
4.使用QtConcurrent
①主要步骤:
- 直接调用QtConcurrent::run函数,将要在子线程中执行的函数作为参数传递进去,该函数可以是普通函数或类的成员函数。
- 如果需要获取子线程函数的返回结果,可以使用QFuture对象来接收QtConcurrent::run函数的返回值,然后通过QFuture的相关函数(如result、waitForFinished等)来获取结果或等待线程完成。
ps:使用时要导入模块 QT += concurrent
②示例:
qDebug()<<"主线程:"<
int Widget::add(int a, int b){
return a+b;
}
QFuture future = QtConcurrent::run(&Widget::add,this,1,2);
int res = future.result();
qDebug()<
三、线程同步
线程同步是指在多线程环境下,通过使用特定的机制来确保不同线程之间的协调和数据一致性。以下是Qt中常用的线程同步机制:
1.互斥锁(QMutex)
互斥锁用于保护共享资源,确保在同一时间只有一个线程能够访问该资源。
使用方法:
- 创建QMutex对象:QMutex mutex;
- 加锁和解锁:在访问共享资源之前,调用lock()方法加锁,访问完成后调用unlock()方法解锁。
2.读写锁(QReadWriteLock)
读写锁提供了一种更高效的方式来管理对共享资源的读写操作,允许多个线程同时进行读操作,但只允许一个线程进行写操作。
使用方法:
- 创建QReadWriteLock对象:QReadWriteLock rwlock;
- 读锁和写锁的获取与释放:在进行读操作前,调用lockForRead()方法获取读锁,读完后调用unlock()方法释放读锁;在进行写操作前,调用lockForWrite()方法获取写锁,写完后调用unlock()方法释放写锁。
3.条件变量(QWaitCondition)
条件变量用于在线程间进行同步,一个线程可以等待某个条件满足后再继续执行,通常与互斥锁结合使用,以避免虚假唤醒。
使用方法:
- 创建QWaitCondition对象:QWaitCondition condition;
- 等待条件满足:在需要等待的线程中,先获取互斥锁,然后调用wait()方法等待条件满足,条件满足后会自动释放互斥锁并继续执行。
- 唤醒等待线程:在其它线程中,当条件满足时,调用wakeOne()或wakeAll()方法唤醒一个或所有等待的线程。
4.信号量(QSemaphore)
信号量用于控制对有限资源的访问,它维护一个计数器,表示可用资源的数量。线程在访问资源前需要获取信号量,如果信号量的值大于0,则表示有可用资源,线程可以继续执行并将信号量的值减1;如果信号量的值为0,则表示没有可用资源,线程需要等待。
使用方法:
- 创建QSemaphore对象:QSemaphore semaphore(3); 【参数表示初始可用资源数量】
- 获取和释放信号量:在访问资源前,调用acquire()方法获取信号量,访问完成后调用release()方法释放信号量。
5.便捷类
QMutexLocker、QReadLocker和QWriteLocker都是便捷类,采用资源获取即初始化(RAII)的模式,实现自动上锁和解锁。
- QMutexLocker:确保互斥锁在QMutexLocker对象的生命周期内被自动上锁,并在该对象被销毁时自动解锁。
- QReadLocker:确保多个线程可以同时获取读取锁,以实现对共享资源的并发读取。
- QWriteLocker:确保同一时间只有一个线程能够获取写入锁,以实现对共享资源的独占写入。