Qt篇: 多线程如何创建之继承QThread

QThread是Qt的一个跨平台解决多线程方案,简单易学,可以让初学者很快上手。QThread是Qt里封装好的一个类,那么既然是类,那么继承QThread的线程就必须以对象的形式被创建和使用,每一个线程都应该对应一个对象。

主要成员函数

  1. run()函数:线程函数体,用于定义线程功能,该函数是线程的执行入口,将一切复杂耗时的业务处理放到该方法当中;
QThread run函数
  1. start()函数:启动函数,将线程入口地址设置为run()函数的地址;
QThread start函数
  1. terminate()函数:强制退出当前线程(不推荐使用),假设如果在线程当中分配了内存,使用该方法可能会导致在线程退出前,没有释放这块内存,就强制退出,这就会造成内存泄露,这也就是不推荐使用terminate()这个方法的根本原因。
QThread terminate函数

QThread线程创建

  1. 继承QThread类,重写run()函数:
// MytThread.cpp

class MyThread : public QThread
{

protected:
  void run() 
  {
    qDebug() << "run() beging..."

    for(int i; i<10; i++)
    {
      qDebug() << objectName() << ": " << i;
      sleep(1);
    }

    qDebug() << "run() end..."
  }

}
  1. 在主线程创建MyThread类实例,并调用start()方法,这就能完整的创建一个QThread的线程。
// main.cpp

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "main begin...";
 
    MyThread t;
    t.start();
 
    qDebug() << "main end...";
    return a.exec();
}

terminate初体验

在工程开发中,terminate()函数是不推荐使用,或者是禁止使用的!terminate()会使得操作系统暴力停止线程,而不会考虑数据的完整性和资源释放等问题。

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
 
class MyThread : public QThread
{
protected:
 
    void run()
    {
        qDebug() << "run() begin...";
 
        // 申请内存
        int *ret = new int[10000];
 
        for( int i=0; i<10; i++ )
        {
            qDebug() << objectName() << ": " << i;
            sleep(1);
            ret[i] = i*i;
        }
 
        qDebug() << "delete ret...";
        delete []ret;
        qDebug() << "run() end...";
    }
};
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "main begin...";
 
    MyThread t;
    t.setObjectName("t");
    t.start();
 
    QThread::sleep(5);
    t.terminate();
 
    qDebug() << "main end...";
    return a.exec();
}

运行结果:

terminate运行结果

从结果来看,程序输出结果并没有打印"delete ret,,,"和"run() end...",可想而知在terminate强制退出后,ret所指向的内存并没有得到释放,造成内存泄露。而且这类产生的bug估计很难察觉到,毕竟很难想到会在主线程里使用terminate()进行强制退出,而造成的内存泄露。

如何优雅的退出线程

  1. run()函数执行结束是优雅终止线程的唯一方式(不仅仅局限于Qt的多线程);
  2. 在线程中增加标志变量m_toStop(volatile bool);
  3. 通过判断m_toStop的值判断是否需要从run()函数返回。

为什么要用volatile修饰m_toStop?

volatile关键字告诉编译器这个变量的值可能会在程序的其他地方发生变化,编译器不要对它进行优化。这在多线程编程中非常重要,因为编译器在优化代码时,可能会将某些变量的值缓存到寄存器中,从而导致其他线程对该变量的修改无法及时反映出来。 具体来说,使用volatile修饰m_toStop有以下好处:

  1. 防止编译器优化:确保每次在循环中检查m_toStop时,都会从内存中读取最新的值,而不是使用寄存器中的缓存值;
  2. 保证变量可见性:对于多线程环境,使用volatile可以保证线程对该变量的修改对其他线程是可见的,从而确保线程能够及时响应停止信号。

完整代码:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
 
class MyThread : public QThread
{
    volatile bool m_toStop;
 
public:
    MyThread()
    {
        m_toStop = false;
    }
 
    // 将标志位设置为true,并优雅终止线程run()函数
    void Stop()
    {
        m_toStop = true;
    }
 
protected:
 
    void run()
    {
        qDebug() << "run() begin...";
 
        // 申请内存
        int *ret = new int[10000];
 
        for( int i=0; ((!m_toStop) && (i<10)); i++ )
        {
            qDebug() << objectName() << ": " << i;
            sleep(1);
            ret[i] = i*i;
        }
 
        qDebug() << "delete ret...";
        delete []ret;
        qDebug() << "run() end...";
    }
};
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "main begin...";
 
    MyThread t;
    t.setObjectName("t");
    t.start();
 
    QThread::sleep(5);
    // 调用Stop()方法
    t.Stop();
 
    qDebug() << "main end...";
    return a.exec();
}