[Qt][Qt 多线程][上]详细讲解
目录
- 0.Qt 多线程概述
- 1.如何看待客户端的多线程?
- 2.Qt 线程的使用条件
- 3.创建线程的方法
- 1.方法一
- 2.方法二
- 3.说明
- 4.关闭线程使用的方法
0.Qt 多线程概述
- Qt中,多线程的处理⼀般是通过
QThread类来实现 QThread代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据QThread对象管理程序中的⼀个控制线程,QThread在run()中开始执⾏- 默认情况下,
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():等待线程任务结束之后关闭线程
