接着上篇继续写道:
三、在子线程中测试排序
一开始编写代码的时候,排序过程是在主线程中完成的,这样的话,一旦数据量级过大(即出现耗时操作),界面就会出现卡死冻结。为了解决这个问题就使用了Qt的子线程。在子线程中进行排序,也就是使用QObject::moveToThread()这个函数。
对于在子线程中编写代码来说,不太向主线程那样好控制(或者),一般来说,适用于把逻辑简单的,操作耗时的操作放在子线程中。在本程序中,不仅把排序操作,还把写入日志操作放进了子线程。然后主线程和子线程通过信号和槽来进行交流。(主线程释放工作信号给子线程,子线程接受信号开始工作,工作完之后,释放工作完成信号,通知主线程。)
主要代码如下:
//排序工作
void SortWorker::RecSortWorkSig(int i, Sort * sorts)
{
sorts->InitData(); //拷贝生成测试数据
//C语言中的函数库,用作微秒级计时
struct timeval tpstart,tpend;
double timeuse;
gettimeofday(&tpstart,NULL);
switch (i) {
case SIM_INS_SORT:
sorts->SimInsSort(); //测试简单插入排序
break;
case SHELL_SORT:
sorts->ShellSort(); //测试希尔插入排序
break;
case BUB_SORT:
sorts->BubSort(); //测试冒泡排序
break;
case QUI_SORT:
sorts->QuiSort(0,sorts->GetDataLength()-1); //测试快速排序
break;
case SIM_SEL_SORT:
sorts->SimSelSort(); //测试简单排序
break;
case HEAP_SORT:
sorts->HeapSort(); //测试堆排序
break;
case MERGE_SORT:
sorts->MergeSort(0,sorts->GetDataLength()-1); //测试二路归并排序
break;
case COUNT_SORT:
sorts->CountSort(); //测试计数排序
break;
case RAD_SORT:
sorts->RadSort(); //测试基数排序
break;
default:
break;
}
gettimeofday(&tpend,NULL);
timeuse=(1000000*(tpend.tv_sec-tpstart.tv_sec) + tpend.tv_usec-tpstart.tv_usec)/1000000.0;
emit SendSortFinishedSig(timeuse); //释放结束信号,传递用时参数给主线程
}
//写入工作
void SortWorker::RecWriLogWorkSig(Sort *sorts,QTextStream *out)
{
(*out)<<"\nOriginal data:";
//写入原始数据
int length = sorts->GetDataLength();
for(int i = 0;i<length;++i)
{
if(i % 10 == 0)
(*out)<<"\n"<<sorts->data0[i];
else
(*out)<<" "<<sorts->data0[i];
}
(*out)<<"\nData after sort:";
for(int i = 0;i<length;++i)
{
if(i % 10 == 0)
(*out)<<"\n"<<sorts->data[i];
else
(*out)<<" "<<sorts->data[i];
}
emit SendWriFinishedSig(); //释放写入完成信号
}
这里需要关联主线程和子线程的信号和槽。 Qt的机制保证这里主线程和子线程的的信号和槽可以相互关联。
//关联信号,用于启动排序新线程并从新线程中读取返回的数据
connect(this,SIGNAL(SendSortWorkSin(int,Sort *)),&sort_worker,SLOT(RecSortWorkSig(int,Sort*)));
connect(&sort_worker,SIGNAL(SendSortFinishedSig(double)),this,SLOT(RecSortFinishedSig(double)));
connect(this,SIGNAL(SendWriWorkSig(Sort*,QTextStream *)),&sort_worker,SLOT(RecWriLogWorkSig(Sort*,QTextStream*)));
connect(&sort_worker,SIGNAL(SendWriFinishedSig()),this,SLOT(RecWriFinishedSig()));
四、等待框的制作
当后台子线程在进行排序和写入日志的过程中,界面不接受用户的输入。此处显示了一个动态的gif等待图片,阻碍主界面。当子线程工作完成之后,释放工作完成信号。主程序接受到信号之后关闭等待框,继续往下执行。如下图所示:
主要代码如下:
//显示等待动画
movie = new QMovie(":/img/wait.gif");
movie->setScaledSize(QSize(ui->label_wait->width(),ui->label_wait->height()));
ui->label_wait->setMovie(movie);
movie->start();
五、测试结果
最终的排序结果将会根据用户的选择是否记录在日志中(比较结果一定会记录,用户可以决定选择是否记录原始数据和排序数据)。注意:日志是的目录是在程序编译的目录,不是源代码的目录。
以下在我电脑上一个数量级为10000的排序结果:
选择的数量规模为:10000
测试比较开始...
简单插入排序开始...
简单插入排序结束。此排序所用时间为:0.166901s
希尔排序开始...
希尔排序结束。此排序所用时间为:0.002999s
冒泡排序开始...
冒泡排序结束。此排序所用时间为:0.48223s
快速排序开始...
快速排序结束。此排序所用时间为:0.002s
简单选择排序开始...
简单选择排序结束。此排序所用时间为:0.192889s
堆排序开始...
堆排序结束。此排序所用时间为:0.003997s
二路归并排序开始...
二路归并排序结束。此排序所用时间为:0.004997s
计数排序开始...
计数排序结束。此排序所用时间为:0.012993s
基数排序开始...
基数排序结束。此排序所用时间为:0.001998s
测试比较结束
本程序最大测试百万级的数据规模,但是对于复杂度为O(N2)的算法来说,十分耗时。比如说冒泡排序,我吃饭之前开始测试,吃完之后,等待框还在继续。但是其他的算法,运行百万级的数据规模还能在可接受的时间内结束。如下图所示:
在数据量较小的时候观察不太明显,数据量一旦很大,O(N2)和O(logN)明显就出来了。
完成的工程代码见:点这儿