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()
:等待线程任务结束之后关闭线程