Qthread应用
前言
重新学习qthread,有一个任务,我们要交给子线程运行,把结果传到主线程,那我们只需要将任务类继承Qthread,重写run函数,在里面写运行逻辑,当主线程需要它运行的时候就会触发start开始子线程,这就是多线程的核心思想。
这里我设计了一个程序,有三个部分,第一个部分lsw1是一个listwidget,显示生成的随机数,第二个显示冒泡排序的结果,第三个生成快速排序的结果,ui如下:
界面搭建就不用讲了,接下来讲讲功能的实现。
为什么采用多线程
大家都知道采用多线程的时候程序运行快,界面不容易出现卡顿,当数据量大的时候,如果在主线程里面执行,他会阻塞主线程的ui刷新造成卡顿现象,就需要把这些事务处理放到子线程里面,主线程只负责ui的更新。
随机数生成
经过上面的讲述,我们随机数生成可以放在一个专门的线程,类命名为Generat,继承Qthread,除了需要重写run函数,我们需要从主线程接受需要生成多少个随机数这样的一个函数
//将主线程中接受的数据赋值给m_numvoid 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限制他的生成范围,第一个参数是最小值,第二个参数是最大值。
类整体展示
//mytread.h
//生成随机数的线程类
class Generate:public QThread
{Q_OBJECT
public:Generate(QObject *parent=nullptr);//将主线程中接受的数据赋值给m_numvoid 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();
};
//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出来
Generate *gen=new Generate;
主程序有一个starting信号,负责把生成多少个随机数发送给子线程,子线程的recnum函数就会接收处理,当界面的按钮点击了以后,就会发送starting信号,并启动随机数线程
//通过信号将要创建多少个随机数传递给子线程connect(this,&MainWindow::starting,gen,&Generate::recnum,Qt::QueuedConnection);//点击按钮后执行子线程connect(ui->pushButton,&QPushButton::clicked,this,[=](){emit starting(10000);gen->start();});
冒泡排序线程
线程的设计和上面差不多,不过他没有下级线程要通知,随机数线程生成了随机数数组后会有个信号传递数组,我们冒泡的线程也接收一份,进行排序逻辑处理
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);//发送时间
}
快速排序线程
同上,逻辑:
//快速排序实现
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这段时间内都废了,十分影响使用体验。