接着上篇繼續寫道:
三、在子線程中測試排序
一開始編寫代碼的時候,排序過程是在主線程中完成的,這樣的話,一旦數據量級過大(即出現耗時操作),界面就會出現卡死凍結。爲了解決這個問題就使用了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)明顯就出來了。
完成的工程代碼見:點這兒