[Qt][Qt 多线程][上]详细讲解


0.Qt 多线程概述

  • Qt中,多线程的处理⼀般是通过QThread类来实现
  • QThread代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据
  • QThread对象管理程序中的⼀个控制线程,QThreadrun()中开始执⾏
    • 默认情况下,run()通过调⽤exec()来启动事件循环,并在线程内运⾏Qt事件循环

1.如何看待客户端的多线程?

  • 服务器利用多线程,最主要的目的是充分利用多核CPU的计算资源
  • 客户端对于普通用户,"使用体验"很重要
    • 客户端上的程序很少会使用多线程把CPU计算资源吃完
    • 主要是利用多线程执行一些耗时的等待IO的操作,避免主线程被卡死,避免对用户造成不好的体验

2.Qt 线程的使用条件

  • 在Qt中,多线程常⽤于⽐较耗时的任务,或只有通过使⽤线程执⾏时才能正常运⾏的情况

3.创建线程的方法

1.方法一

  • 继承QThread类,重写run()函数 --> 多态
  • 步骤
    • ⾃定义⼀个类,继承于QThread,并且只有⼀个线程处理函数重写⽗类中的run()
    • 线程处理函数⾥⾯写⼊需要执⾏的复杂数据处理
    • 启动线程不能直接调⽤run(),需要使⽤对象来调⽤start()实现线程启动
      • start()底层就是调用OS API创建线程,新县城创建后自动执行run()
    • 线程处理函数执⾏结束后可以定义⼀个信号来告诉主线程
    • 最后关闭线程
  • 示例:定时器
    // thread.h
    class Thread : public QThread
    {
        Q_OBJECT
    public:
        Thread();
    
        // 重要的目的是重写父类的 run 方法.
        void run();
    
    signals:
        void Notify();
    };
    
    // thread.cpp
    void Thread::run()
    {
        // 每过一秒, 通过信号槽, 来通知主线程, 负责更新的界面内容
        for (int i = 0; i < 10; i++) 
        {
            // sleep 本身是 QThread 的成员函数, 就可以直接调用
            sleep(1);
            
            // 发送一个信号, 通知主线程
            emit Notify();
        }
    }
    -------------------------------------------------------------------------
    // widget.h
    class Widget : public QWidget
    {
    	// ...
        Thread thread;
        void Handle();
    };
    
    // widget.cpp
    // 构造函数中
    {
    	// 连接信号槽, 通过槽函数更新界面
    	connect(&thread, &Thread::Notify, this, &Widget::Handle);
    	
    	// 要启动一下线程.
    	thread.start();
    }
    
    void Widget::handle()
    {
        // 此处修改界面内容.
        int value = ui->lcdNumber->intValue();
        value--;
        ui->lcdNumber->display(value);
    }
    

2.方法二

  • 继承QObject类,通过moveToThread(thread),交给thread执⾏
  • 函数原型moveToThread(QThread* targetThread)
  • 功能:将⼀个对象移动到指定的线程中运⾏
  • 步骤
    • ⾃定义⼀个类,继承于QObject
    • 创建⼀个⾃定义线程类的对象,不能指定⽗对象
    • 创建⼀个QThread类的对象,可以指定其⽗对象
    • 将⾃定义线程对象加⼊到QThread类的对象中使⽤
    • 使⽤start()启动线程
      • 调⽤start()只是启动了线程,但是并没有开启线程处理函数
      • 线程处理函数的开启需要⽤到信号槽机制
    • 关闭线程
  • 示例
    // thread.h
    class MyThread : public QObject
    {
    	Q_OBJECT
    public:
    	// ...
    	void Thread();
    	void SetFlag(bool flag = true);
    
    signals:
    	void Notify();
    	
    private:
    	bool isStop() = false;
    };
    
    // thread.cpp
    void MyThread::Thread()
    {
    	while(!isStop)
    	{
    		QThread::sleep(1);
    		
    		emit Notofy();
    
    		qDebug() << " ⼦线程号: " << QThread::currentThread();
    
    		if(isStop)
    		{
    			break;
    		}
    	}
    }
    
    void MyThread::SetFlag(bool flag)
    {
    	isStop = flag;
    }
    -------------------------------------------------------------------------
    // widget.h
    class Widget : public QWidget
    {
    	// ...
    	MyThread* myThread;
    	QThread* thread;
    
    signals:
    	void StartSignal(); // 启动子线程信号
    
    private slot:
    	void on_startPushbutton_clicked();
    	void on_closePushbutton_clicked();
    	void DelSignals();
    	void DealClose();
    };
    
    // widget.cpp
    // 构造函数中
    {
    	// 动态分配空间,不能指定⽗对象
    	myThread = new MyThread();
    
    	// 创建子线程
    	thread = new QThread(this);
    
    	// 将⾃定义的线程加⼊到⼦线程中
    	myThread->moveToThread(thread);
    
    	connect(myThread, &MyThread::Notify, this, &Widget::DelSignals);
    	connect(this, &Widget::StartSignal, myThread, &MyThread::Thread);
    	connect(this, &Widget::destroyed, this, &Widget::DealClose);
    }
    
    void MyWidget::on_startPushbutton_clicked()
    {
    	if(thread->isRunning() == true)
    	{
    		return;
    	}
    
    	// 启动线程但是没有启动线程处理函数
    	thread->start();
    
    	// 不能直接调⽤线程处理函数,直接调⽤会导致线程处理函数和主线程处于同⼀线程
    	emit StartSignal();
    }
    
    void MyWidget::DelSignals()
    {
    	static int i = 0;
    	i++;
    	ui->lcdNumber->display(i);
    }
    
    void MyWidget::on_closePushbutton_clicked()
    {
    	if(thread->isRunning() == false)
    	{
    		return;
    	}
    
    	myThread->SetFlag();
    	thread->quit();
    	thread->wait();
    }
    
    void MyWidget::DealClose()
    {
    	delete myThread;
    	on_closePushbutton_clicked();
    }
    

3.说明

  • 线程函数内部不允许操作UI图形界⾯,⼀般⽤数据处理
    • 只有主线程可以操作UI图形界面
  • connect()第五个参数表⽰的是连接的⽅式,且只有在多线程的时候才意义
  • connect()第五个参数Qt::ConnectionType,⽤于指定信号和槽的连接类型,同时影响信号的传递⽅式和槽函数的执⾏顺序
    • Qt::AutoConnection:会根据信号和槽函数所在的线程⾃动选择连接类型
      • 如果信号和槽函数在同⼀线程中,那么使⽤Qt:DirectConnection类型
      • 如果它们位于不同的线程中,那么使⽤Qt::QueuedConnection类型
    • Qt::DirectConnection:当信号发出时,槽函数会⽴即在同⼀线程中执⾏
      • 适⽤于信号和槽函数在同⼀线程中的情况
      • 可以实现直接的函数调⽤,但需要注意线程安全性
    • Qt::QueuedConnection:当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下⼀次事件循环时执⾏
      • 适⽤于信号和槽函数在不同线程中的情况,可以确保线程安全
    • Qt::BlockingQueuedConnection:与Qt:QueuedConnection类似
      • 但是发送信号的线程会被阻塞,直到槽函数执⾏完毕
      • 适⽤于需要等待槽函数执⾏完毕再继续的场景,但需要注意可能引起线程死锁的⻛险
    • Qt::UniqueConnection:这是⼀个标志,可以使⽤|与上述任何⼀种连接类型组合使⽤

4.关闭线程使用的方法

  • void terminate():直接关闭线程不等待线程任务结束
  • void quit():等待线程任务结束之后关闭线程
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DieSnowK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值