多线程编程(一)
①QThread
在Qt中,多线程的处理一般是通过QThread
类来实现。
QThread
代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。
QThread
对象管理程序中的一个控制线程。
run() | 线程的入口函数 |
start() | 通过调用run()开始执行函数,操作系统将根据优先级参数调度线程,如果线程已经在运行,这个函数什么也不做 |
currentThread() | 返回一个指向管理当前执行线程的QThread的指针 |
isRunning() | 如果线程正在运行则返回true,否则返回false |
sleep() / msleep() / usleep() | 使线程休眠,单位为秒/毫秒/微秒 |
wait() | 阻塞线程,直到满足以下任何一个条件:
|
terminate() | 终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。在terminate()之后使用QThread:wait()来确保。 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。 |
②两种多线程使用方式
1>第一种
Qt中提供的多线程的第一种使用方式的特点是:简单
- 需要创建一个线程类的子类,让其继承QT中的线程类QThread,例如:
class MyThread:public QThread { ...... }
- 重写父类中的run()方法,在该函数内部编写子线层要处理的具体的业务流程
class MyThread:public QThread { ...... protected: void run() { ........ } }
- 在主线程中new一个子线程对象,在主线程中合适的位置启动子线程,调用start()方法
MyThread * subThread = new MyThread; subThread->start();
不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行
当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:
- 在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了
- 只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程
这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。
示例:生成随机数,然后使用冒泡排序和快速排序对其进行处理
创建类的方法就不多说了
mythread.h
#ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread> #include<QVector> //生成随机数,将构造类的名字直接改成Generate更明确一些 class Generate : public QThread { Q_OBJECT public: explicit Generate(QObject *parent = nullptr); //将主线程传递过来的数保存到m_num void recvNum(int num); protected: void run() override; signals: void sendArray(QVector<int>); private: int m_num; }; class BubbleSort : public QThread { Q_OBJECT public: explicit BubbleSort(QObject *parent = nullptr); //将主线程传递过来的数保存到m_num void recvArray(QVector<int> list); protected: void run() override; signals: void finish(QVector<int>); private: QVector<int> m_list; }; class QuickSort : public QThread { Q_OBJECT public: explicit QuickSort(QObject *parent = nullptr); //将主线程传递过来的数保存到m_num void recvArray(QVector<int> list); protected: void run() override; private: void quickSort(QVector<int> &list,int l, int r); signals: void finish(QVector<int>); private: QVector<int> m_list; }; #endif // MYTHREAD_H
mythread.cpp
#include "mythread.h" #include<QElapsedTimer> #include<QDebug> Generate::Generate(QObject *parent) : QThread(parent) { } void Generate::recvNum(int num) { m_num = num; } void Generate::run() { qDebug() << "生成随机数的线程的线程地址:" << QThread::currentThread(); QVector<int> list; //计时 QElapsedTimer time; time.start(); for(int i=0; i<m_num; ++i) { list.push_back(qrand() % 100000); } int milsec = time.elapsed(); qDebug() << "生成" << m_num << "随机数总用时:" << milsec << "毫秒"; //发送给主线程 emit sendArray(list); } BubbleSort::BubbleSort(QObject *parent):QThread(parent) { } void BubbleSort::recvArray(QVector<int> list) { m_list = list; } void BubbleSort::run() { qDebug() << "冒泡排序的线程的线程地址:" << QThread::currentThread(); //计时 QElapsedTimer time; time.start(); //冒泡排序 int temp; for(int i=0;i<m_list.size();++i) { for(int j=0;j<m_list.size()-i-1;++j) { if(m_list[j] > m_list[j+1]) { temp = m_list[j]; m_list[j] = m_list[j+1]; m_list[j+1] = temp; } } } int milsec = time.elapsed(); qDebug() << "冒泡排序用时:" << milsec << "毫秒"; emit finish(m_list); } QuickSort::QuickSort(QObject *parent):QThread(parent) { } void QuickSort::recvArray(QVector<int> list) { m_list = list; } void QuickSort::run() { qDebug() << "快速排序的线程的线程地址:" << QThread::currentThread(); //计时 QElapsedTimer time; time.start(); //快速排序 quickSort(m_list,0,m_list.size()-1); int milsec = time.elapsed(); qDebug() << "快速排序用时:" << milsec << "毫秒"; emit finish(m_list); } void QuickSort::quickSort(QVector<int> &s, int l, int r) { if(l<r) { int i = l,j = r; int x = s[l]; while(i < j) { while(i < j && s[j] >= x) { j--; } if(i < j) { s[i++] = s[j]; } while(i < j && s[i] < x) { i++; } if(i < j) { s[j--] = s[i]; } } s[i] = x; quickSort(s,l,i-1); quickSort(s,i+1,r); } }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); signals: void starting(int num); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include<mythread.h> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); //1.创建子线程对象 Generate* gen = new Generate; BubbleSort* bubble = new BubbleSort; QuickSort* quick = new QuickSort; connect(this,&MainWindow::starting,gen,&Generate::recvNum); //2.启动子线程 connect(ui->start,&QPushButton::clicked,this,[=]() { emit starting(10000); gen->start(); }); //随机数子线程发送来的数据触发冒泡排序和快速排序接收数据 connect(gen,&Generate::sendArray,bubble,&BubbleSort::recvArray); connect(gen,&Generate::sendArray,quick,&QuickSort::recvArray); //接收子线程发送的数据,显示在randlist里,同时启动两个排序子线程 connect(gen,&Generate::sendArray,this,[=](QVector<int> list) { //启动两个子线程方法 bubble->start(); quick->start(); for (int i=0; i<list.size(); ++i) { ui->randlist->addItem(QString::number(list.at(i))); } }); //两个排序子线程处理数据 connect(bubble,&BubbleSort::finish,this,[=](QVector<int> list) { for (int i=0; i<list.size(); ++i) { ui->bubblelist->addItem(QString::number(list.at(i))); } }); connect(quick,&QuickSort::finish,this,[=](QVector<int> list) { for (int i=0; i<list.size(); ++i) { ui->quicklist->addItem(QString::number(list.at(i))); } }); //资源释放 connect(this,&MainWindow::destroyed,this,[=]() { //线程释放 gen->quit(); gen->wait(); gen->deleteLater(); bubble->quit(); bubble->wait(); bubble->deleteLater(); quick->quit(); quick->wait(); quick->deleteLater(); }); } MainWindow::~MainWindow() { delete ui; }
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); //向 Qt 元对象系统注册 QVector<int> 类型 qRegisterMetaType<QVector<int>>("QVector<int>"); MainWindow w; w.show(); return a.exec(); }
为什么需要注册类型?
在 Qt 中,某些类型在信号和槽机制中传递时需要被注册。Qt 默认支持一些基本类型(如 int
、double
、QString
等),但是对于自定义类型或者某些复杂类型(如 QVector<int>
),需要显式注册。
- 信号和槽机制: Qt 的信号和槽机制允许对象之间进行通信。当一个信号发出时,连接到该信号的槽函数会被调用。如果你希望信号或槽函数使用
QVector<int>
这样的类型,你需要向 Qt 注册该类型,这样 Qt 才能在运行时识别和处理这种类型。 - QMetaType 系统: Qt 的元对象系统(QMetaType)需要知道所有使用的类型,以便能够在运行时进行类型转换、创建对象和处理信号槽连接等操作。通过
qRegisterMetaType
注册类型,可以确保 Qt 的元对象系统能够识别并处理这种类型。
2>第二种
Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些
- 创建一个新的类,让其从QObject派生(QObject类提供
moveToThread()
方法)
class MyWork:public QObject { ....... }
- 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
class MyWork:public QObject { public: ....... // 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加 void working(); }
- 在主线程中创建一个QThread对象,这就是子线程的对象
QThread* sub = new QThread;
- 在主线程中创建工作的类对象
MyWork* work = new MyWork(this); // 错误,这里不能指定this //如果指定了父类为this,那就无法移动任务到其他的线程中 MyWork* work = new MyWork; // 正确,什么也不用指定
- 将MyWork对象移动到创建的子线程对象中,需要调用QObject类提供的
moveToThread()
方法
// void QObject::moveToThread(QThread *targetThread); // 如果给work指定了父对象, 这个函数调用就失败了,也就是第四步中提到的this // 提示: QObject::moveToThread: Cannot move objects with a parent work->moveToThread(sub); // 移动到子线程中工作
- 启动子线程,调用start(),这时候线程启动了,但是移动到线程中的对象并没有工作
- 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的
示例:生成随机数,然后使用冒泡排序和快速排序对其进行处理
直接复制了上边示例的项目,所以这里的类名还是一致的,但是这种方法是从QObject中派生的,命名在这里用于区分代码就好
mythread.h
#ifndef MYTHREAD_H #define MYTHREAD_H #include <QObject> #include<QVector> //生成随机数,将构造类的名字直接改成Generate更明确一些 class Generate : public QObject { Q_OBJECT public: explicit Generate(QObject *parent = nullptr); void working(int num); signals: void sendArray(QVector<int>); }; class BubbleSort : public QObject { Q_OBJECT public: explicit BubbleSort(QObject *parent = nullptr); void working(QVector<int> list); signals: void finish(QVector<int>); }; class QuickSort : public QObject { Q_OBJECT public: explicit QuickSort(QObject *parent = nullptr); void working(QVector<int> list); private: void quickSort(QVector<int> &list,int l, int r); signals: void finish(QVector<int>); }; #endif // MYTHREAD_H
mythread.cpp
#include "mythread.h" #include<QElapsedTimer> #include<QDebug> #include<QThread> Generate::Generate(QObject *parent) : QObject(parent) { } void Generate::working(int num) { qDebug() << "生成随机数的线程的线程地址:" << QThread::currentThread(); QVector<int> list; //计时 QElapsedTimer time; time.start(); for(int i=0; i<num; ++i) { list.push_back(qrand() % 100000); } int milsec = time.elapsed(); qDebug() << "生成" << num << "随机数总用时:" << milsec << "毫秒"; //发送给主线程 emit sendArray(list); } BubbleSort::BubbleSort(QObject *parent):QObject(parent) { } void BubbleSort::working(QVector<int> list) { qDebug() << "冒泡排序的线程的线程地址:" << QThread::currentThread(); //计时 QElapsedTimer time; time.start(); //冒泡排序 int temp; for(int i=0;i<list.size();++i) { for(int j=0;j<list.size()-i-1;++j) { if(list[j] > list[j+1]) { temp = list[j]; list[j] = list[j+1]; list[j+1] = temp; } } } int milsec = time.elapsed(); qDebug() << "冒泡排序用时:" << milsec << "毫秒"; emit finish(list); } QuickSort::QuickSort(QObject *parent):QObject(parent) { } void QuickSort::working(QVector<int> list) { qDebug() << "快速排序的线程的线程地址:" << QThread::currentThread(); //计时 QElapsedTimer time; time.start(); //快速排序 quickSort(list,0,list.size()-1); int milsec = time.elapsed(); qDebug() << "快速排序用时:" << milsec << "毫秒"; emit finish(list); } void QuickSort::quickSort(QVector<int> &s, int l, int r) { if(l<r) { int i = l,j = r; int x = s[l]; while(i < j) { while(i < j && s[j] >= x) { j--; } if(i < j) { s[i++] = s[j]; } while(i < j && s[i] < x) { i++; } if(i < j) { s[j--] = s[i]; } } s[i] = x; quickSort(s,l,i-1); quickSort(s,i+1,r); } }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); signals: void starting(int num); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include<mythread.h> #include<QThread> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); //1.创建子线程对象 QThread* t1 = new QThread; QThread* t2 = new QThread; QThread* t3 = new QThread; //2.创建任务类的对象 Generate* gen = new Generate; BubbleSort* bubble = new BubbleSort; QuickSort* quick = new QuickSort; //3.将任务对象移动到某个子线程中 gen->moveToThread(t1); bubble->moveToThread(t2); quick->moveToThread(t3); connect(this,&MainWindow::starting,gen,&Generate::working); //2.启动子线程 connect(ui->start,&QPushButton::clicked,this,[=]() { emit starting(10000); t1->start(); }); //随机数子线程发送来的数据触发冒泡排序和快速排序接收数据 connect(gen,&Generate::sendArray,bubble,&BubbleSort::working); connect(gen,&Generate::sendArray,quick,&QuickSort::working); //接收子线程发送的数据,显示在randlist里,同时启动两个排序子线程 connect(gen,&Generate::sendArray,this,[=](QVector<int> list) { //启动两个子线程方法 t2->start(); t3->start(); for (int i=0; i<list.size(); ++i) { ui->randlist->addItem(QString::number(list.at(i))); } }); //两个排序子线程处理数据 connect(bubble,&BubbleSort::finish,this,[=](QVector<int> list) { for (int i=0; i<list.size(); ++i) { ui->bubblelist->addItem(QString::number(list.at(i))); } }); connect(quick,&QuickSort::finish,this,[=](QVector<int> list) { for (int i=0; i<list.size(); ++i) { ui->quicklist->addItem(QString::number(list.at(i))); } }); //资源释放 connect(this,&MainWindow::destroyed,this,[=]() { //线程释放 t1->quit(); t1->wait(); t1->deleteLater(); t2->quit(); t2->wait(); t2->deleteLater(); t3->quit(); t3->wait(); t3->deleteLater(); //任务对象释放 gen->deleteLater(); bubble->deleteLater(); quick->deleteLater(); }); } MainWindow::~MainWindow() { delete ui; }
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); qRegisterMetaType<QVector<int>>("QVector<int>"); MainWindow w; w.show(); return a.exec(); }
connect函数第五个参数
connect()
函数第五个参数为Qt:ConnectionType
,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。只有在多线程的时候才意义。
Qt:ConnectionType提供了以下五种方式:
Qt:AutoConnection | 在Qt中,会根据信号和槽函数所在的线程自动选择连接类型。如果信号和槽函数在同一线程中,那么使用Qt:DirectConnection类型;如果它们位于不同的线程中,那么使用Qt:QueuedConnection类型。 |
Qt:DirectConnection | 当信号发出时,槽函数会立即在同一线程中执行。这种连接类型适用于信号和槽函数在同一线程中的情况,可以实现直接的函数调用,但需要注意线程安全性。 |
Qt::QueuedConnection | 当信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下一次事件循环时执行。这种连接类型适用于信号和槽函数在不同线程中的情况,可以确保线程安全。 |
Qt:BlockingQueuedConnection | 与 |
Qt:UniqueConnection | 这是一个标志,可以使用位或与上述任何一种连接类型组合使用。 |