在qt中使用了多线程,有些事项是需要额外注意的:
- 默认的线程在qt中称之为窗口线程,也叫主线程(UI线程)。负责窗口事件处理或者控件数据的更新。
- 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理。
- 主线程和子线程之间如果需要进行数据的传递,需要使用qt的信号槽机制。
1. 线程类QThread
Qt中提供一个线程类,通过这个类就可以创建子线程了,Qt中一共提供了两种创建子线程的方式。这个类的常用API函数:
public函数 | |
QThread(QObject *parent = nullptr) | 构造函数 |
virtual ~QThread() | 析构函数 |
QAbstractEventDispatcher * eventDispatcher() const | |
bool isFinished() const | 判断线程中的任务是不是处理完毕了 |
bool isInterruptionRequested() const | |
bool isRunning() const | 判断子线程是不是在执行任务 |
int loopLevel() const | |
QThread::Priority priority() const | 得到当前线程的优先级 |
void requestInterruption() | |
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher) | |
void setPriority(QThread::Priority priority) | 设置线程优先级 |
void setStackSize(uint stackSize) | |
uint stackSize() const | |
bool wait(QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever)) | |
bool wait(unsigned long time) | 等待任务完成,然后退出线程,一般情况会在exit()后边调用这个函数 |
槽函数 | |
void exit(int returnCode = 0) | 退出线程,停止底层的事件循环 |
void quit() | 和调用exit()效果是一样的,使用这个函数后,再调用wait()函数 |
void start(QThread::Priority priority = InheritPriority) | 启动子线程 |
void terminate() | 线程退出,可能是会马上终止线程,一般情况下不使用这个函数 |
信号函数 | |
void finished() | 线程中执行的任务完成了发出该信号,,任务函数中的处理逻辑执行完毕 |
void started() | 开始工作之前发出这个信号,一般不使用 |
静态公共成员 | |
QThread * create(Function &&f, Args &&... args) | |
QThread * currentThread() | 返回一个指向管理当前执行线程的QThread的指针 |
Qt::HANDLE currentThreadId() | |
int idealThreadCount() | 返回可以在系统上运行的理想线程数 == 当前电脑的CPU核心数相同 |
void msleep(unsigned long msecs) | 线程休眠函数 毫秒 |
void sleep(unsigned long secs) | 线程休眠函数 秒 |
void usleep(unsigned long usecs) | 线程休眠函数 微秒 |
void yieldCurrentThread() | |
受保护的函数 | |
int exec() | |
virtual void run() | 子线程要处理什么任务,需要写到run()中 |
2. 使用方式 1
2.1 操作步骤
QT中提供的多线程的第一种使用方式的特点是:简单。操作步骤如下:1. 需要创建一个线程的子类,让其继承QT中的线程类QThread,比如:
1. 创建一个类对象
class MyThread : public QThread
{
...
}
2. 重写父类run()方法,在该函数内部编写子线程要处理的具体的业务流程
class MyThread : public QThread
{
... ...
protected:
void run()
{
... ...
}
}
3. 在主线程中创建子线程对象,new 一个就可以了
MyThread* subThread = new MyThread;
4. 启动子线程,调用start()方法
subThread->start();
不能在类的外部调用run()方法启动子线程,在外部调用start()相当于让run()开始运行,当子线程创建出来后,父子线程之间的通信可以通过信号槽的方式,注意事项:
- 在Qt中在子线程中不要操作程序中的窗口类型的对象,不允许,如果操作的了程序就挂了
- 只有主线程才能操作程序中的窗口对象,默认的线程就是主线程,自己创建的就是子线程
3. 使用方式 2
3.1 操作步骤
Qt提供的第二种线程的创建方式弥补了第一种的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,其具体操作步骤如下:
1.创建一个新的类,让这个类从QObject派生
class MyWork : public QObject
{
... ...
}
2. 这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
class MyWork : public QObject
{
public:
... ...
//函数名自己指定,叫什么都可以,参数可以根据实际需求添加
void working();
}
3. 在主线程中创建一个QThread对象,这就是子线程的对象
QThread* sub = new QThread;
4. 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
MyWork* work = new MyWork;
5. 将MyWork 对象移动到创建的子线程对象中,需要调用QObject类提供的moveToThread()方法
//移动到子线程中工作
work->moveToThread(sub);
6. 启动子线程,调用start(),这时候线程就启动了,但是移动到线程中的对象并没有工作
7. 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的
部分代码:MyThread类的代码
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
//生产随机数
class Generate : public QThread
{
Q_OBJECT
public:
Generate(QObject *parent);
~Generate();
public slots:
void RecvNum(int num);
protected:
void run() override;
signals:
void sendArray(QVector<int> num);
private:
int m_num;
};
class BubbleSort : public QThread
{
Q_OBJECT
public:
BubbleSort(QObject* parent);
~BubbleSort();
public slots:
void RecvArray(QVector<int> list);
protected:
void run() override;
signals:
void finish(QVector<int> num);
private:
QVector<int> m_list;
};
class QuickSort : public QThread
{
Q_OBJECT
public:
QuickSort(QObject* parent);
~QuickSort();
public slots:
void RecvArray(QVector<int> list);
protected:
void run() override;
signals:
void finish(QVector<int> num);
private:
QVector<int> m_list;
void quickSort(QVector<int>& list, int l, int r);
};
#endif
#include "MyThread.h"
#include <QElapsedTimer>
#include <QDebug>
Generate::Generate(QObject *parent) : QThread(parent)
{}
Generate::~Generate()
{}
void Generate::RecvNum(int num)
{
m_num = num;
}
void Generate::run()
{
QElapsedTimer time;
time.start();
qDebug() << "生成随机数的线程的线程地址 " << QThread::currentThread();
QVector<int> list;
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)
{
}
BubbleSort::~BubbleSort()
{
}
void BubbleSort::RecvArray(QVector<int> list)
{
m_list = list;
}
void BubbleSort::run()
{
QElapsedTimer time;
time.start();
qDebug() << "冒泡排序的线程的线程地址 " << QThread::currentThread();
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])
{
auto 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)
{
}
QuickSort::~QuickSort()
{
}
void QuickSort::RecvArray(QVector<int> list)
{
m_list = list;
}
void QuickSort::run()
{
QElapsedTimer time;
time.start();
qDebug() << "快速排序的线程的线程地址 " << QThread::currentThread();
quickSort(m_list,0,m_list.size() -1);
int milsec = time.elapsed();
qDebug() << " 快速排序用时: " << milsec << "毫秒";
emit finish(m_list);
}
void QuickSort::quickSort(QVector<int>& list, int l, int r)
{
if (l < r)
{
int i = l, j = r;
//拿出第一个元素,保存到x中, 第一个位置成为一个坑
int x = list[l];
while (i < j)
{
//从右向左找最小的x的数
while (i < j && list[j] >= x)
{
//左移,直到遇到小于等于x的数
j--;
}
if (i < j)
{
//将右侧找到的小于x的元素放入左侧的坑中,右侧出现一个坑,左侧元素索引后移
list[i++] = list[j];
}
while (i < j && list[i] < x)
{
//右移,知道遇到大于x的数
i++;
}
if ( i < j)
{
//将左侧找到的元素放到右侧坑中,左侧出现一个坑,右侧元素索引向前移动
list[j--] = list[i];
}
}
//此时 i = j,将保存在x中的数填入坑中
list[i] = x;
quickSort(list,l,i - 1);
quickSort(list,i + 1, r);
}
}
第二种 开线程的代码:
workEEGThread->moveToThread(_GPUThread.get());
emit RunGPUThreadWorking();
_GPUThread->start();
这个就比较简单了,思路就是,设置一个工作类,然后再设置一个工作线程,这里工作类是workEEGThread 工作线程是GPUThread,由于workEEGThread继承与OQbject所以里面有个通用的方法,moveToThread,就是把工作类移动到线程中,所以这个workEEGThread->moveToThread(_GPUThread.get());是把workEEGThread线程移动到_GPUThread线程中。
但是需要注意一点就是需要一个信号槽函数,告诉线程你要启动那个函数。
void EEGData::Registration()
{
connect(this, &EEGData::RunGPUThreadWorking, workEEGThread.get(), &WorkEEGThread::working);
}
这就是emit RunGPUThreadWorking();这行代码的原因。然后_GPUThread->start();运行这行代码就可以启动线程了。线程函数就进入了workEEGThread类的working函数中,working是由信号槽函数指定的。之前没有指定信号槽函数,怎么调试都进不去新线程中,所以这个记录一下。