多线程
1. 线程概念的起源
- 单核CPU
早期还没有线程的概念,如何保证2个进程同时进行呢?时间片轮转调度
每次被CPU选中来执行当前进程所用的时间,时间一到,无论进程是否运行结束,操作系统都会强制将CPU这个资源转到另一个进程去执行。 - 多核CPU
随着运行的进程越来越多,人们发现进程的创建、撤销与切换存在着较大的时空开销,因此业界急需一种轻型的进程技术来减少开销。于是上世纪80年代出现了一种叫SMP的对称多处理技术,就是我们所知的线程概念。
线程切换的开销要小很多,这是因为每个进程都有属于自己的一个完整虚拟地址空间,而线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间,基本上就可以利用进程所拥有的资源而无需调用新的资源,故对它的调度所付出的开销就会小很多。
2. 三种方式创建线程
- 线程入口函数
- 全局函数
#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(); });
- 继承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();
}
- 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(); // 原始栈空间 0
th3.setStackSize(1024 * 1024 * 3);
qDebug() << "现在栈空间" << th3.stackSize();//现在栈空间 3145728
4. 启动线程/退出线程
- 启动线程
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);
}