Qt中的多线程

先来了解一些多线程的相关概念

进程和线程

在计算机的操作系统中,有进程线程的划分

多线程

操作系统可以同时执行多个任务,每个任务就对应一个进程,每个进程又可以同时执行多个任务,每个任务就对应一个线程

线程之间相互独立,以抢占式的方式执行,对于单核CPU来说同一时刻只能有一个进程执行,一个线程执行,其实无论是单核还是多核CPU,操作系统都营造出了可以同时运行多个程序的假象,实际操作系统对进程的调度和CPU的处理是通过快速切换上下文实现的,每个进程执行一会儿就先停下来,然后CPU切换到下一个被操作系统调度的程序上使之运行,因为切换的很快,我们很难观察出来,就会让我们以为操作系统一直在运行我们的程序

为什么要使用多线程?

(1)对于多核cpu,多线程程序可以充分利用硬件优势

(2)对于单核cpu,由于线程上下文的切换会降低程序整体运行效率。并且为了防止执行耗时操作时界面假死,我们必须把耗时操作单独放在线程中后台执行,防止阻塞主线程无法刷新窗口

阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

(1)阻塞:在调用结果返回前,当前线程会被挂起,并在得到结果之后返回,对于线程之间的公共资源,同时只能由一个线程操作,在此期间其他线程的访问将会被挂起到上一次访问结束的状态

(2)非阻塞:如果不能立刻得到结果,则该调用者不会阻塞当前线程

同步和异步

同步和异步关注的是消息通信机制

(1)同步: 就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了,换句话说,就是由调用者主动等待这个调用的结果,通俗点讲,执行一个操作必须等待执行完成,下面的逻辑才会继续执行,就是同步

(2)异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果, 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用,通俗点讲,对函数调用后,不会等待执行结果,继续执行下面的代码,就是异步

OK,理解完了多线程的相关概念,我们来看看如何在Qt中使用多线程

Qt中使用多线程

Qt中使用多线程有两种方式,分别是Qt4.7之前和之后的方式

Qt4.7之前的方式更简单,Qt4.7之后的方式更灵活

Qt4.7之前的方式

通过继承QThread重写run()函数实现

①新建一个类MyThread,继承QThread,重写run()虚函数(run即线程处理函数)

#include
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    void run() override;  //线程处理函数

signals:
    void sign_stop(); //线程结束信号

};
void MyThread::run()
{
    //要处理的任务... 例如耗时3秒的操作
    QThread::sleep(3); 
    emit sign_stop(); //线程结束发送信号
}

②主线程中创建MyThread的对象,通过调用start()来调用子线程

MyThread* mythread = new Mythread;
mythread->start(); //调用子线程

Qt4.7之后的方式

将一个继承自QObject的类对象通过moveToThread(QThread*)转移到一个线程中

①新建一个类继承自QObject,自定义一个线程处理函数

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    void Thread_deal();//线程处理函数
signals:
    void sign_stop(); //线程结束信号
};
void MyThread::Thread_deal()
{
    //要处理的任务... 例如耗时3秒的操作
    QThread::sleep(3);
    emit sign_stop(); //发送线程结束信号
}

②主线程中创建MyThread和QThread对象,通过moveToThread()将mythread加入到线程中,自定义一个线程开始的信号,通过signal-slot关联触发线程开始信号时调用线程处理函数

signals:
void start_thread();//线程开启信号

MyThread* mythread = new MyThread; //注意此处不能指定父对象
QThread* thread = new QThread(this);
mythread->moveToThread(thread);//若mythread指定父对象,此处报错
connect(this,&MainWindow::start_thread,mythread,&MyThread::Thread_deal);

③在需要启动线程的地方通过调用start(),发送线程开始信号,通过信号槽调用线程处理函数,

在启动线程前应先判断该线程是否在活动状态

if(thread->isRunning())
        return;
    thread->start();//启动线程,但没有启动线程处理函数
    emit start_thread();

④关闭线程,这点很重要,不然很容易出异常

thread->quit();/*线程的事件循环退出并返回代码0(成功),相当于调用QThread::exit(0)。
如果线程没有事件循环,则此函数不执行任何操作*/
thread->wait();//阻塞线程,直到满足以下任一条件
delete mythread;//由于分配空间时为指定父对象,需要我们手动释放

⑤验证,QThread::currentThreadId()可查看当前线程ID号,发现确实是不同的线程

多线程使用过程中注意事项:

1)线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)

2)需要移动到子线程中处理的模块类,创建对象时,不能指定父对象,因为调用moveToThread函数之后,变量在创建的线程中使用回收,而不是在主线程

3)主线程通过信号和槽的方式调用子线程处理函数,主线程发送信号给子线程,槽函数就是子线程的线程处理函数

4)对于Qt4.7之前的方式,槽函数在创建线程类对象得线程(一般是主线程)中执行

5)对于Qt4.7之后的方式,槽函数在子线程中执行,通过信号槽调用,直接调用则都在调用线程中执行,所以要把耗时操作放在槽函数中,外面信号触发

多线程的快速停止

其实在使用多线程,调用quit(),stop()关闭线程的时候,线程并不会直接停止,而是告诉线程要停止,然后会相当于再执行一次线程函数,等待线程函数的结束,想要快速停止就必须我们自定义一个线程停止的变量,在线程函数中对这个变量进行判断,为真则return立即退出线程的事件循环,然后我们就可以在我们想要停止线程的地方调用Thread_stop()表示我们想要停止线程

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    void Thread_deal();//线程处理函数
    void Thread_stop();//线程快速停止
signals:
    void sign_stop(); //线程结束信号
private:
    bool Is_stop = false; //线程停止变量
};
void MyThread::Thread_deal()
{
    while(i<10000)
   {
     //非常复杂的数据处理...
      if(Is_stop)
        return;
   } 
    emit sign_stop(); //发送线程结束信号
}

void MyThread::Thread_stop()
{
    Is_stop = true; //修改线程停止变量为真
}

connect()第五个参数

我们平常使用connect()时一般都只写4个参数,但其实它有5个参数,平常情况下第5个参数是默认值,但使用多线程,就需要涉及到第5个参数了

1)Qt::AutoConnection,这就是默认值,这个值会在信号槽连接时自动判断,信号和槽在同一线程时,直接连接(DirectConnection ),不同线程时,队列连接(QueueConnection)

2)Qt::DirectConnection,直接连接,槽函数会在信号被发送的时候直接调用,同步调用,不依赖QT事件循环emit发送信号后面的代码将在与信号关联的所有槽函数执行完毕后才被执行,无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行

3)Qt::QueueConnection,队列连接,信号发出后,信号会被暂时放在一个消息队列中,需要等待接收对象的线程取得线程的控制权时,才取出该信号,然后执行与槽关联的槽函数,这种方式既可以在同一线程内传递消息,也可以跨线程操作,eimt发送信号后面的代码将会立即被执行,无需等待槽函数执行完毕,槽函数在接受者的线程执行,异步调用,槽函数所在对象得线程必须启用QT事件循环

4)Qt::BlockingQueuedConnection 阻塞连接,同步调用, 槽函数在接受者的线程中执行,不过发送信号完后发送者所在的线程会阻塞,直到槽函数运行完毕,而且接受者和发送者绝对不能在同一个线程,否则程序会死锁,在多线程需要同步的情况可能会使用这个

5)Qt::UniqueConnection,这是一个flag,可以通过位或(|)与上面四个结合在一起使用,当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败,也就是避免了信号槽的重复连接

以上就是Qt中多线程的使用

创作不易,这篇文章对你有帮助的话就点个赞吧~

点赞收藏关注就是对我最大的支持~

你可能感兴趣的:(Qt,开发语言,qt,c++)