当前位置:   article > 正文

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

[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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

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():等待线程任务结束之后关闭线程
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/1005471
推荐阅读
相关标签
  

闽ICP备14008679号