一文看懂Qt多线程

目录

一、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.常用方法

  1. start():用于启动线程,调用后会执行run()函数,在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。
  2. run():线程的入口点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个函数,便于管理自己的线程。该方法返回时,该线程的执行将结束。
  3. quit():告诉线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。
  4. exit(int returnCode = 0):告诉线程事件循环退出。调用这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,0表示成功,任何非0值表示失败。
  5. terminate():终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait(),以确保万无一失。当线程被终止后,所有等待中的线程将会被唤醒。此函数比较危险,不鼓励使用。
  6. wait(unsigned long time = ULONG_MAX):线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。
  7. isFinished():判断线程是否结束。
  8. isRunning():判断线程是否正在运行。
  9. setPriority(Priority priority):设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。
  10. currentThreadId():返回标识当前正在执行的线程的平台特定的ID。
  11. currentThread():返回一个指向管理当前执行线程的QThread的指针。
  12. msleep(unsigned long msecs):强制当前线程睡眠msecs毫秒。
  13. sleep(unsigned long secs):强制当前线程睡眠secs秒。
  14. usleep(unsigned long usecs):强制当前线程睡眠usecs微秒。

   

2.信号

  1. finished():线程执行结束时发射此信号。
  2. started():线程启动时发射此信号。
  3. terminated():线程被终止时发射此信号。

   

3.注意事项

  • 在使用QThread时,需要注意线程安全问题,尤其是在多个线程访问共享资源时,需要使用互斥锁等机制进行同步。
  • 在主线程中创建的对象不能直接在子线程中使用,需要通过信号和槽等方式进行传递和操作。
  • 尽量避免使用terminate()函数来终止线程,因为它可能导致资源泄漏和数据不一致等问题。

   


   

二、多线程实现方式

1.继承QThread类并重写run函数

①主要步骤:

  1. 创建线程类:自定义一个类继承自QThread类,在类中重写run函数,将需要在子线程中执行的代码放在run函数内。
  2. 启动线程:在主线程中创建自定义类的实例,然后调用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()

①主要步骤:

  1. 创建工作类:创建一个继承自QObject的类,在该类中定义需要在子线程中执行的槽函数。
  2. 创建线程和对象实例:在主线程中创建一个QThread实例和工作类的实例。
  3. 移动对象到线程:调用继承自MyObject类的moveToThread函数,将其移动到创建的QThread线程中。
  4. 启动线程并连接信号槽:启动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

①主要步骤:

  1. 创建一个继承自QRunnable的类,在该类中重写run函数,将需要在子线程中执行的代码放在run函数内。
  2. 在主线程中获取全局线程池实例QThreadPool::globalInstance(),然后创建MyRunnable类的实例。
  3. 调用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

①主要步骤:

  1. 直接调用QtConcurrent::run函数,将要在子线程中执行的函数作为参数传递进去,该函数可以是普通函数或类的成员函数。
  2. 如果需要获取子线程函数的返回结果,可以使用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)

互斥锁用于保护共享资源,确保在同一时间只有一个线程能够访问该资源。

使用方法:

  1. 创建QMutex对象:QMutex mutex;
  2. 加锁和解锁:在访问共享资源之前,调用lock()方法加锁,访问完成后调用unlock()方法解锁。

    

2.读写锁(QReadWriteLock)

读写锁提供了一种更高效的方式来管理对共享资源的读写操作,允许多个线程同时进行读操作,但只允许一个线程进行写操作。

使用方法:

  1. 创建QReadWriteLock对象:QReadWriteLock rwlock;
  2. 读锁和写锁的获取与释放:在进行读操作前,调用lockForRead()方法获取读锁,读完后调用unlock()方法释放读锁;在进行写操作前,调用lockForWrite()方法获取写锁,写完后调用unlock()方法释放写锁。

   

3.条件变量(QWaitCondition)

条件变量用于在线程间进行同步,一个线程可以等待某个条件满足后再继续执行,通常与互斥锁结合使用,以避免虚假唤醒。

使用方法:

  1. 创建QWaitCondition对象:QWaitCondition condition;
  2. 等待条件满足:在需要等待的线程中,先获取互斥锁,然后调用wait()方法等待条件满足,条件满足后会自动释放互斥锁并继续执行。
  3. 唤醒等待线程:在其它线程中,当条件满足时,调用wakeOne()或wakeAll()方法唤醒一个或所有等待的线程。

  

4.信号量(QSemaphore)

信号量用于控制对有限资源的访问,它维护一个计数器,表示可用资源的数量。线程在访问资源前需要获取信号量,如果信号量的值大于0,则表示有可用资源,线程可以继续执行并将信号量的值减1;如果信号量的值为0,则表示没有可用资源,线程需要等待。

使用方法:

  1. 创建QSemaphore对象:QSemaphore semaphore(3); 【参数表示初始可用资源数量】
  2. 获取和释放信号量:在访问资源前,调用acquire()方法获取信号量,访问完成后调用release()方法释放信号量。

  

5.便捷类

QMutexLocker、QReadLocker和QWriteLocker都是便捷类,采用资源获取即初始化(RAII)的模式,实现自动上锁和解锁。

  • QMutexLocker:确保互斥锁在QMutexLocker对象的生命周期内被自动上锁,并在该对象被销毁时自动解锁。
  • QReadLocker:确保多个线程可以同时获取读取锁,以实现对共享资源的并发读取。
  • QWriteLocker:确保同一时间只有一个线程能够获取写入锁,以实现对共享资源的独占写入。

  
 

你可能感兴趣的:(qt,开发语言,多线程)