Qt第十七章 多线程

server/2024/9/24 14:22:42/

文章目录

  • 多线程
    • 1. 线程概念的起源
    • 2. 三种方式创建线程
    • 3. 启动线程前的准备工作
    • 4. 启动线程/退出线程
    • 5. 操作运行中的线程
    • 6. 为每个线程提供独立数据
    • 7.子线程不能操作ui
      • 解决方案

多线程

1. 线程概念的起源

  • 单核CPU
    早期还没有线程的概念,如何保证2个进程同时进行呢?时间片轮转调度
    每次被CPU选中来执行当前进程所用的时间,时间一到,无论进程是否运行结束,操作系统都会强制将CPU这个资源转到另一个进程去执行。
  • 多核CPU
    随着运行的进程越来越多,人们发现进程的创建、撤销与切换存在着较大的时空开销,因此业界急需一种轻型的进程技术来减少开销。于是上世纪80年代出现了一种叫SMP的对称多处理技术,就是我们所知的线程概念。
    线程切换的开销要小很多,这是因为每个进程都有属于自己的一个完整虚拟地址空间,而线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间,基本上就可以利用进程所拥有的资源而无需调用新的资源,故对它的调度所付出的开销就会小很多。

2. 三种方式创建线程

  1. 线程入口函数
  • 全局函数
#include <QThread>
void helloThread()
{for (int i = 0; i < 99999999; ++i) {qDebug() << "hello";}
}int main(int argc, char* argv[])
{QApplication a(argc, argv);QThread* th1 = QThread::create(helloThread);th1->start();return a.exec();
}
void helloThread(QString s)
{for (int i = 0; i < 99999999; ++i) {qDebug() << "hello" << s;}
}int main(int argc, char* argv[])
{QApplication a(argc, argv);QThread* th1 = QThread::create(helloThread, "thread1");th1->start();return a.exec();
}
  • 静态成员函数
class A {
public:A() { }static void print(){for (int i = 0; i < 10; i++)qDebug() << __FUNCTION__;}
};int main(int argc, char* argv[])
{QApplication a(argc, argv);QThread* th1 = QThread::create(A::print);th1->start();return a.exec();
}
  • 成员函数
class A {
public:A() { }void print(){for (int i = 0; i < 10; i++)qDebug() << __FUNCTION__;}
};int main(int argc, char* argv[])
{QApplication a(argc, argv);A aFunc;QThread* th1 = QThread::create(&A::print, &aFunc);th1->start();return a.exec();
}
  • lamda表达式
    QThread* th2 = QThread::create([=] { qDebug() << "lamda"; });th2->start();
  • 连接信号与槽
    QObject::connect(th2, &QThread::started, [=] { qDebug() << "th2线程启动"; });QObject::connect(th1, &QThread::finished, [=] { qDebug() << "th1线程执行完毕";th1->deleteLater(); });
  1. 继承QThread重写run
class MyThread : public QThread {
public:void run() override{for (int i = 0; i < 10; i++) {qDebug() << "MyThread" << i;if (i == 5)quit(); // 只能在线程中调用,但是要等事件结束才结束}}
};
int main(int argc, char* argv[])
{QApplication a(argc, argv);MyThread th3;th3.start();th3.exit(); // 结束线程,也要等事件结束才结束    return a.exec();    
}
  1. QObject::moveToThread
class Download : public QObject {Q_OBJECT
public:Download(QObject* parent = nullptr): QObject(parent){}
public slots:void downloadFile(){for (int i = 0; i < 100; i++)qDebug() << "下载进度=====" << i << "%";emit finished();}
signals:void finished();
};class APP : public QObject {Q_OBJECT
public:APP(QObject* parent = nullptr): QObject(parent){d = new Download;th = new QThread;connect(this, &APP::startDownload, d, &Download::downloadFile);connect(d, &Download::finished, [=] { qDebug() << "下载完成"; });d->moveToThread(th);connect(th, &QThread::finished, th, &QThread::deleteLater);th->start();}
signals:void startDownload();private:Download* d;QThread* th;
};
int main(int argc, char* argv[])
{QApplication a(argc, argv);APP ap;emit ap.startDownload();return a.exec();
}#include "main.moc"

3. 启动线程前的准备工作

    // 建议最多同时开多少线程数qDebug() << QThread::idealThreadCount();// 改变栈空间,如果线程运行所占空间很大,那就会崩溃,需要改变qDebug() << "原始栈空间" << th3.stackSize(); // 原始栈空间 0th3.setStackSize(1024 * 1024 * 3);qDebug() << "现在栈空间" << th3.stackSize();//现在栈空间 3145728

4. 启动线程/退出线程

  • 启动线程

调用start()函数后,新线程会优先执行run()中的代码,再执行其他的
默认run()会调用exec()函数,即启动一个局部的不占CPU的事件循环

5. 操作运行中的线程

  • 休眠函数
void helloThread(QString s)
{for (int i = 0; i < 9; ++i) {qDebug() << "hello" << s;QThread::sleep(1); // 睡眠1秒QThread::msleep(100); // 睡眠100毫秒QThread::usleep(1000); // 睡眠1000微秒}
}
  • 中断标志
class MyThread : public QThread {
public:void run() override{for (int i = 0; i < 10; i++) {qDebug() << "MyThread" << i;// 中断标志if (this->isInterruptionRequested())break;// 请求中断if (i == 3)this->requestInterruption();}}
};

在这里插入图片描述

6. 为每个线程提供独立数据

关于全局变量,在2个线程里修改会相互影响

int g_num = 5;
void fun()
{g_num = 8;qInfo() << "g_num" << g_num;
}
void fun1()
{g_num = 9;qInfo() << "1_g_num" << g_num;
}
int main(int argc, char* argv[])
{QApplication a(argc, argv);QThread* th = QThread::create(fun);QThread* th1 = QThread::create(fun1);th->start();th1->start();return a.exec();
}

在这里插入图片描述
可以通过QThreadStorage类把全局变量设置成线程独立的变量

QThreadStorage<int> g_num;
void fun()
{g_num.setLocalData(8);qInfo() << "g_num" << g_num.localData();
}
void fun1()
{g_num.setLocalData(9);qInfo() << "1_g_num" << g_num.localData();
}
int main(int argc, char* argv[])
{QApplication a(argc, argv);QThread* th = QThread::create(fun);QThread* th1 = QThread::create(fun1);th->start();th1->start();qDebug() << "main_g_num" << g_num.localData();    return a.exec();
}

在这里插入图片描述

7.子线程不能操作ui

Gui框架一般只允许ui线程操作界面组件,Qt也是如此,否则会出现崩溃

解决方案

  • 通过信号与槽
    参考之前的moveToThread

  • 通过QMetaObject::invokeMethod

#include "Widget.h"
#include "./ui_Widget.h"
#include <QMetaObject>
#include <QThread>Widget::Widget(QWidget* parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QThread* th = QThread::create(&Widget::fun, this);th->start();
}Widget::~Widget()
{delete ui;
}void Widget::fun()
{QThread::sleep(5);QMetaObject::invokeMethod(ui->label, "setText", Q_ARG(QString, "hello"));
}void Widget::on_pushButton_clicked()
{fun();
}
  • 通过QApplication::postEvent
#include "Widget.h"
#include "./ui_Widget.h"
#include <QCoreApplication>
#include <QMetaObject>
#include <QThread>class MyEvent : public QEvent {
public:MyEvent(): QEvent(QEvent::Type(QEvent::User)){}
};Widget::Widget(QWidget* parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::fun()
{QCoreApplication::postEvent(this, new MyEvent);
}void Widget::customEvent(QEvent* ev)
{if (ev->type() == QEvent::User)this->move(-10, 0);
}

http://www.ppmy.cn/server/103834.html

相关文章

android13顶部状态栏里面调节背光 背景闪烁问题

总纲 android13 rom 开发总纲说明 目录 1.前言 2.问题分析 3.代码分析 4.代码修改 5.彩蛋 1.前言 android13顶部状态栏里面调节背光, 背景闪烁问题,会出现画面不全问题,如下图 2.问题分析 这里看起来是由于隐藏的时候,界面显示是一个渐变的隐藏,但是后面的背景又是…

QT基础知识5

思维导图 client.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), socket(new QTcpSocket(this))//给客户端实例化分配空间 {ui->setupUi(this);//初始化界面ui->msgEdit-&…

pair 与 tie功能与用法-清晰教程

目录 一、什么是 pair 1.1 定义 1.2 功能 1.3 使用方法 1.3.1 创建 pair 1.3.2 用法场景 1.3.3 其他操作 二、什么是 tie 2.1 定义 2.2 功能 2.3 使用方法 2.3.1 基本用法 2.3.2 忽略部分值 2.3.3 使用场景 三、pair 与 tie 的比较 3.1 功能比较 3.2 用途比较…

PCIe学习笔记(26)

Error Forwarding&#xff08;错误转发&#xff09; 错误转发(也称为数据中毒)&#xff0c;通过设置EP位表示。下面是一些使用错误转发的例子: •例#1:从主存读取遇到不可纠正的错误 •例#2:PCI写到主存的奇偶校验错误 •例#3:内部数据缓冲区或缓存上的数据完整性错误 错误…

【开端】记一次诡异的接口排查过程

一、绪论 最近碰到这么一个情况&#xff0c;接口请求超时。前提是两台服务器间的网络是畅通的&#xff0c;端口也是通&#xff0c;应用代码也是通。意思是在应用上&#xff0c;接口没有任何报错&#xff0c;能正常返回数据。客户端到服务端接口也能通&#xff0c;但是接收不到服…

八:《Python基础语法汇总》— 面向对象

一&#xff1a;面向对象基础 1.类和对象&#xff1a; &#xff08;1&#xff09;类 ​ 类是对一系列具有相同特征和行为的事物的统称&#xff0c;是一个抽象的概念&#xff0c;不是真实存在的事物&#xff0c;而对象就是根据类来创建的&#xff0c;有类才有对象 ​ 特征 --…

炒作将引发人工智能寒冬

我们似乎经常看到人工智能的进步被吹捧为机器真正变得智能的一大飞跃。我将在这里挑选其中的一个例子&#xff0c;并确切解释为什么这种态度会为人工智能的未来埋下隐患。 这很酷&#xff0c;这是一个非常困难且非常具体的问题&#xff0c;这个团队花了3 年时间才解决。他们一定…

StarRocks 存算分离数据回收原理

前言 StarRocks存算分离表中&#xff0c;垃圾回收是为了删除那些无用的历史版本数据&#xff0c;从而节约存储空间。考虑到对象存储按照存储容量收费&#xff0c;因此&#xff0c;节约存储空间对于降本增效尤为必要。 在系统运行过程中&#xff0c;有以下几种情况可能会需要删…