前言
重新学习qthread,有一个任务,我们要交给子线程运行,把结果传到主线程,那我们只需要将任务类继承Qthread,重写run函数,在里面写运行逻辑,当主线程需要它运行的时候就会触发start开始子线程,这就是多线程的核心思想。
这里我设计了一个程序,有三个部分,第一个部分lsw1是一个listwidget,显示生成的随机数,第二个显示冒泡排序的结果,第三个生成快速排序的结果,ui如下:

界面搭建就不用讲了,接下来讲讲功能的实现。
为什么采用多线程
大家都知道采用多线程的时候程序运行快,界面不容易出现卡顿,当数据量大的时候,如果在主线程里面执行,他会阻塞主线程的ui刷新造成卡顿现象,就需要把这些事务处理放到子线程里面,主线程只负责ui的更新。
随机数生成
经过上面的讲述,我们随机数生成可以放在一个专门的线程,类命名为Generat,继承Qthread,除了需要重写run函数,我们需要从主线程接受需要生成多少个随机数这样的一个函数
//将主线程中接受的数据赋值给m_num
void recnum(int num);
m_num是这个类的成员函数,还需要在我们排序执行完了以后,需要有一个信号,把执行结果传递给主线程,他们之间用connect的Qt::QueuedConnection模式进行通讯 ,和运行了多久的计时器时间也要通过信号传递
//生成的序列发送信号
void sendarray(QVector<int> num);
//发送用时
void sendtime(int time);
计时器的选用
计时器采用的是QElapsedTimer类,这是一个毫秒级的计时器,当程序运行过快的时候,会出现一个问题,比如运行时间只有1ms,太小了,他就会为0,碰到这种情况有两个解决办法
1.增加数据量,延长程序的运行时间
2.采用精度更高的计时器
run函数实现
随机数刷新用到了QRandomGenerator类,先上我使用的方法
for (int i = 0; i < m_num; ++i) {
list.push_back(QRandomGenerator::global()->bounded(0,10000));
}
该类下global是一个随机数生成器,bounded限制他的生成范围,第一个参数是最小值,第二个参数是最大值。
类整体展示
cpp
//mytread.h
//生成随机数的线程类
class Generate:public QThread
{
Q_OBJECT
public:
Generate(QObject *parent=nullptr);
//将主线程中接受的数据赋值给m_num
void recnum(int num);
// QThread interface
private:
int m_num;
protected:
void run() override;
signals:
//生成的序列发送信号
void sendarray(QVector<int> num);
//发送用时
void sendtime(int time);
//随机数生成完毕,通知排序工作线程开始工作
void startsort();
};
cpp
//mythread.cpp
void Generate::recnum(int num)
{
m_num=num;
}
//生成随机数线程
void Generate::run()
{
QVector<int>list;
QElapsedTimer timer;
timer.start();
for (int i = 0; i < m_num; ++i) {
list.push_back(QRandomGenerator::global()->bounded(0,10000));
}
int milsec=timer.elapsed();
emit sendarray(list);
emit sendtime(milsec);
emit startsort();
}
有一个信号是后面加的start sort,在他的下一级还有两个工作线程,他相当于是个生产者,生产好了以后通知消费者开始用这些数据工作。
这样线程类就做好了,在主程序中做好connect接收子线程的信号传递的数据,在ui上显示就行了。
信号的处理绑定
主线程中首先把子线程类的对象new出来
cpp
Generate *gen=new Generate;
主程序有一个starting信号,负责把生成多少个随机数发送给子线程,子线程的recnum函数就会接收处理,当界面的按钮点击了以后,就会发送starting信号,并启动随机数线程
cpp
//通过信号将要创建多少个随机数传递给子线程
connect(this,&MainWindow::starting,gen,&Generate::recnum,Qt::QueuedConnection);
//点击按钮后执行子线程
connect(ui->pushButton,&QPushButton::clicked,this,[=](){
emit starting(10000);
gen->start();
});
冒泡排序线程
线程的设计和上面差不多,不过他没有下级线程要通知,随机数线程生成了随机数数组后会有个信号传递数组,我们冒泡的线程也接收一份,进行排序逻辑处理
cpp
void Bubblesort::run()
{
QElapsedTimer time;//采用秒级计时器,如果数据太少执行太快会导致运行时间太短显示运行0秒
time.start();
int temp;
//升序冒泡
for (int i = 0; i < m_num.size(); ++i) {
for (int j = 0; j < m_num.size()-i-1; ++j) {
if(m_num.at(j)>m_num.at(j+1)){
temp=m_num.at(j);
m_num[j]=m_num[j+1];
m_num[j+1]=temp;
}
}
}
int sec=time.elapsed();
emit sendarray(m_num);//发送排序后的数组
emit sendtime(sec);//发送时间
}
快速排序线程
同上,逻辑:
cpp
//快速排序实现
void Quicksort::quicksort(QVector<int> &arr, int left, int right)
{
if (left >= right) return;
int i = left;
int j = right;
// 取中间值作为基准,避免最坏情况
int pivot = arr[(left + right) / 2];
while (i <= j) {
while (arr[i] < pivot) i++; // 找左边大于等于基准的
while (arr[j] > pivot) j--; // 找右边小于等于基准的
if (i <= j) {
// 交换元素
std::swap(arr[i], arr[j]);
i++;
j--;
}
}
// 递归排序子数组
quicksort(arr, left, j);
quicksort(arr, i, right);
}
void Quicksort::run()
{
QElapsedTimer time;
time.start();
quicksort(m_num,0,m_num.size()-1);
int sec=time.elapsed();
emit sendarray(m_num);//发送数组
emit sendtime(sec);//发送时间
}
效果
剩余的就是在主线程做一些信号绑定就行了,运行效果如下:

由此看到,当遇到数据量比较大的时候,冒泡排序是非常慢的,如果放到主线程跑,ui这段时间内都废了,十分影响使用体验。