前言
去年操作系统的课程设计,要求设计一个多线程并行排序的程序,记录一下
一、关键结构体
因为线程创建函数用的是unsigned __stdcall function(void *p)
,所以这里传参需要用到结构体指针进行参数传递。对于快速排序和二路归并排序,每一次递归都需要传递不同的参数,包括:数组指针,起始位置,结束位置 等,所以需要一个结构体来传递这些参数。
结构体具体定义为:
struct QckSortThread
{
int* p; //指向数组的指针
int i; //起始
int j; //终止
int f; //记录当前层数
};
这里的变量f
的作用是为了记录生成树的层数,用于控制生成线程的数量。所以这里简单描述一下线程生成的过程。直接上图:
我们期望的线程创建过程是如上图这样的,因为传统的快速排序和二路归并排序都是不停对原数组进行划分来进行排序,划分后一般是从左到右,从下到上进行归并。而我们需要多线程并行排序的目标为,如图所示的,在进行一次划分后,划分后的数组同时进行下一次划分或者同时向上归并,以此来达到并行的目的。
而具体的最高并行数取决于CPU的核心数,所以我们需要控制生成线程的数量来达到最高效率。如果线程数过多,反而会浪费资源在线程的切换调度上,从而降低效率。
所以这里的结构体QckSortThread
中的变量f
的作用就是用于记录层数,用层数来控制生成的线程的数目。3层二叉树的叶子节点也就是需要做最大运算量的线程数就是22 总共生成的线程数就是23
二、多线程快速排序
1.快速排序算法
数据结构都学过,就不过多解释了。
代码如下:
void quick_sort(int* num, int star, int end)
{
int start = star; //不变起始
int ending = end; //不变末位
int key = num[start];
while (star < end)
{
while ((star < end) && (num[end] >= key))
{
end--;
}
num[star] = num[end];
while ((star < end) && ((num[star]) <= key))
{
star++;
}
num[end] = num[star];
}
num[star] = key;
if ((star - start) > 1)
{
quick_sort(num,start,star-1);
}
if ((ending - end) > 1)
{
quick_sort(num, star+1, ending);
}
}
2.多线程快速排序函数
代码如下:
/*快排多线程递归*/
unsigned __stdcall Sort_1(void* p)
{
/*
取出传进来的值
*/
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++; //层数+1
int start = star; //不变起始
int ending = end; //不变末位
int key = num[start];
/*
下面是和快速排序相似的逻辑,将数组分成两组,然后保存关键位置,可以对照上面看一看
*/
while (star < end)
{
while ((star < end) && (num[end] >= key))
{
end--;
}
num[star] = num[end];
while ((star < end) && ((num[star]) <= key))
{
star++;
}
num[end] = num[star];
}
num[star] = key;
/*
下面是创建线程
*/
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
//将数组前半部分传入新线程进行计算
QckSortThread* p1 = new QckSortThread;
p1->i = start;
p1->j = star - 1;
p1->p = num;
p1->f = f1;
if ( (star -start) > 1 && (f1 < level)) //判断是否符合进一步递归条件,否则直接调用单线程快排算法进行归并
{
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
}
else
{
quick_sort(num, start, star - 1);
}
//将后半部分传入新线程,同上
QckSortThread* p2 = new QckSortThread;
p2->i = star + 1;
p2->j = ending;
p2->p = num;
p2->f = f1;
if ((ending - end) > 1&& (f1 < level) )
{
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
}
else
{
quick_sort(num, star + 1, ending);
}
delete[]p; //一定要释放掉!!!!不然结果会不正确,而且不报错!!!
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
return 1;
}
代码思路很简单,逻辑和快速排序差不多。添加了层数的判断,如果层数达到预设值的值或者已经不能再进行分组,就进行单线程排序,否则创建线程继续排序。
三、多线程二路归并排序
1.二路归并算法
数据结构有讲具体算法,不多赘述。
代码如下:
void Merge(int* a, int low, int mid, int high)
{
int i = low, j = mid + 1, p = low;//对应a数组的下标
//int* r = new int[high - low + 1];//申请另一个对应大小的数组来存放排好序的数据
while (i <= mid && j <= high)
{
r[p++] = (a[i] <= a[j]) ? a[i++] : a[j++];
}
while (i <= mid)
r[p++] = a[i++];
while (j <= high)
r[p++] = a[j++];
for (p = low, i = low; i <= high; p++, i++)
a[i] = r[p];//最后再把有序数据存进a数组中,使得a数组对应部分数据有序
}
void MSort(int* a, int low, int high)
{
if (low < high )
{
int mid = (low + high) / 2;
MSort(a, low, mid);
MSort(a, mid + 1, high);
Merge(a, low, mid, high);
}
}
这里把原算法Merge
中的int* r = new int[high - low + 1];
改掉了,因为在并行算法中不断的申请和释放空间会非常非常影响排序的效率,所以这里在函数Merge
中的数组r[]
是一个预先开辟好空间的全局变量,大小至少大于你需要排序的数量。在之后的并行排序中,各线程就直接在全局变量的对应位置中进行排序操作,不需要重复地申请和释放空间,可节省很多资源。
2.多线程二路归并算法
/*二路归并多线程递归*/
unsigned __stdcall Sort_3(void* p)
{
//传参和分割数据
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++;//记录层数
//和MSort一样的算法逻辑可以对照着看一看
if (star < end)
{
int mid = (star + end) / 2;
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
//这里判断思想和快速排序一样
if (f1 < level)
{
QckSortThread* p1 = new QckSortThread;
p1->i = star;
p1->j = mid;
p1->p = num;
p1->f = f1;
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p1, 0, &threadID_1);
}
else
{
MSort(num, star, mid);
}
if (f1 < level)
{
QckSortThread* p2 = new QckSortThread;
p2->i = mid + 1;
p2->j = end;
p2->p = num;
p2->f = f1;
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p2, 0, &threadID_2);
}
else
{
MSort(num, mid + 1, end);
}
delete[]p;//一定一定要释放!!!!
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
//这里在分组分结束后需要在向上归并的时候进行Merge排序
Merge(num, star, mid, end);
}
return 1;
}
这里并行排序的思想和快排很像,就不用多讲了。主要是全局变量的开辟会节省很多时间,读者起始可以试一试不使用全局变量,使用传统算法会慢多少。
四、所有代码
这里将所有代码全部贴出来,包括生成随机数,随机数导出txt,排序导出txt,不同线程数的对比,不同算法所用的时间,需要的可以参考一下。
// 多线程测试.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <Windows.h>
#include <iostream>
#include <process.h>
#include <time.h>
#include<cstdio>
#include<fstream>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;
double time1[6];
int NUM[3000000]; //待排数组
int r[3000000]; //临时存储数组
int w =3000000; //记录数据量
int level = 2; // 记录层数
int level_2 = 10;//最大层数
struct QckSortThread
{
int* p; //指向数组的指针
int i; //起始
int j; //终止
int f; //记录当前层数
};
/*二项归并*/
void Merge(int* a, int low, int mid, int high)
{
int i = low, j = mid + 1, p = low;//对应a数组的下标
//int* r = new int[high - low + 1];//申请另一个对应大小的数组来存放排好序的数据
while (i <= mid && j <= high)
{
r[p++] = (a[i] <= a[j]) ? a[i++] : a[j++];
}
while (i <= mid)
r[p++] = a[i++];
while (j <= high)
r[p++] = a[j++];
for (p = low, i = low; i <= high; p++, i++)
a[i] = r[p];//最后再把有序数据存进a数组中,使得a数组对应部分数据有序
}
//自顶向下(递归实现)
void MSort(int* a, int low, int high)
{
if (low < high )
{
int mid = (low + high) / 2;
MSort(a, low, mid);
MSort(a, mid + 1, high);
Merge(a, low, mid, high);
}
}
/*快排函数*/
void quick_sort(int* num, int star, int end)
{
int start = star; //不变起始
int ending = end; //不变末位
int key = num[start];
while (star < end)
{
while ((star < end) && (num[end] >= key))
{
end--;
}
num[star] = num[end];
while ((star < end) && ((num[star]) <= key))
{
star++;
}
num[end] = num[star];
}
num[star] = key;
if ((star - start) > 1)
{
quick_sort(num,start,star-1);
}
if ((ending - end) > 1)
{
quick_sort(num, star+1, ending);
}
}
/*写随机数*/
void WriteRand()
{
FILE* fp;
string ch;
srand((unsigned)time(NULL));
for (int i = 0; i < w; i++)
{
NUM[i] = rand() + rand() + rand() ;
}
fopen_s(&fp, ".\Source.txt", "w");
for (int i = 0; i < w; i++)
{
fprintf(fp, "%d ", NUM[i]);
}
fclose(fp);
}
/*写文件*/
void Write(const char* filename)
{
FILE* fp_1;
fopen_s(&fp_1, filename, "w");
for(int i = 0; i < w; i++)
fprintf(fp_1, "%d ", NUM[i]);
}
/*快排多线程递归*/
unsigned __stdcall Sort_1(void* p)
{
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++;
int start = star; //不变起始
int ending = end; //不变末位
int key = num[start];
while (star < end)
{
while ((star < end) && (num[end] >= key))
{
end--;
}
num[star] = num[end];
while ((star < end) && ((num[star]) <= key))
{
star++;
}
num[end] = num[star];
}
num[star] = key;
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
QckSortThread* p1 = new QckSortThread;
p1->i = start;
p1->j = star - 1;
p1->p = num;
p1->f = f1;
if ( (star -start) > 1 && (f1 < level))
{
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
}
else
{
quick_sort(num, start, star - 1);
}
QckSortThread* p2 = new QckSortThread;
p2->i = star + 1;
p2->j = ending;
p2->p = num;
p2->f = f1;
if ((ending - end) > 1&& (f1 < level) )
{
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
}
else
{
quick_sort(num, star + 1, ending);
}
delete[]p;
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
return 1;
}
/*二路+快排的递归线程1.0*/
unsigned __stdcall Sort_2(void* p)
{
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++;
int mid = (star + end) / 2;
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
QckSortThread* p1 = new QckSortThread;
p1->i = star;
p1->j = mid;
p1->p = num;
p1->f = f1;
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
QckSortThread* p2 = new QckSortThread;
p2->i = mid + 1;
p2->j = end;
p2->p = num;
p2->f = f1;
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
delete[]p;
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
Merge(num, star, mid, end);
return 0;
}
/*二路归并多线程递归*/
unsigned __stdcall Sort_3(void* p)
{
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++;
if (star < end)
{
int mid = (star + end) / 2;
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
if (f1 < level)
{
QckSortThread* p1 = new QckSortThread;
p1->i = star;
p1->j = mid;
p1->p = num;
p1->f = f1;
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p1, 0, &threadID_1);
}
else
{
MSort(num, star, mid);
}
if (f1 < level)
{
QckSortThread* p2 = new QckSortThread;
p2->i = mid + 1;
p2->j = end;
p2->p = num;
p2->f = f1;
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p2, 0, &threadID_2);
}
else
{
MSort(num, mid + 1, end);
}
delete[]p;
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
Merge(num, star, mid, end);
}
return 1;
}
/*二路+快排的递归线程2.0*/
unsigned __stdcall Sort_5(void* p)
{
int star = ((QckSortThread*)p)->i;
int end = ((QckSortThread*)p)->j;
int* num = ((QckSortThread*)p)->p;
int f1 = ((QckSortThread*)p)->f;
f1++;
if (star < end)
{
int mid = (star + end) / 2;
HANDLE hHandle_1 = 0;
HANDLE hHandle_2 = 0;
unsigned threadID_1;
unsigned threadID_2;
QckSortThread* p1 = new QckSortThread;
p1->i = star;
p1->j = mid;
p1->p = num;
p1->f = f1;
if (f1 < level)
{
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)p1, 0, &threadID_1);
}
else
hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
QckSortThread* p2 = new QckSortThread;
p2->i = mid + 1;
p2->j = end;
p2->p = num;
p2->f = f1;
if (f1 < level)
{
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)p2, 0, &threadID_2);
}
else
hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
delete[]p;
WaitForSingleObject(hHandle_1, INFINITE);
WaitForSingleObject(hHandle_2, INFINITE);
CloseHandle(hHandle_1);
CloseHandle(hHandle_2);
Merge(num, star, mid, end);
}
return 0;
}
void readfile()
{
FILE* file;
fopen_s(&file, ".\Source.txt", "r");
for (int i = 0; i < w; i++)
fscanf_s(file, "%d", &NUM[i]);
}
int main()
{
clock_t start, end;//计时
HANDLE hThread;
unsigned threadID;
QckSortThread* date = new QckSortThread;
WriteRand();
int* num = NUM;
while (level <= level_2)
/*
多线程快排
*/
{
readfile();
QckSortThread* data = new QckSortThread;
data->p = num;
data->i = 0;
data->j = w - 1;
data->f = 0;
start = clock();
hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)data, 0, &threadID);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
end = clock();
double time = (double)(end - start) / CLOCKS_PER_SEC;
time1[0] = time;
cout << "\n多线程快速排序time: " << time;
//Write("./多线程快排.txt");
/*
* 单线程快排
*/
readfile();
start = clock();
quick_sort(num, 0, w - 1);
end = clock();
time = (double)(end - start) / CLOCKS_PER_SEC;
time1[1] = time;
cout << "\n单线程快速排序time: " << time;
//Write("./单线程快排.txt");
/*
多线程归并排序
*/
readfile();
data = new QckSortThread;
data->p = num;
data->i = 0;
data->j = w - 1;
data->f = 0;
start = clock();
hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)data, 0, &threadID);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
end = clock();
time = (double)(end - start) / CLOCKS_PER_SEC;
time1[2] = time;
cout << "\n多线程归并排序time: " << time;
//Write("./多线程归并.txt");
/*
* 单线程归并排序
*/
readfile();
start = clock();
MSort(num, 0, w - 1);
end = clock();
time = (double)(end - start) / CLOCKS_PER_SEC;
time1[3] = time;
cout << "\n单线程归并排序time: " << time;
//Write("./单线程归并.txt");
/*多线程快速+归并*/
readfile();
data = new QckSortThread;
data->p = num;
data->i = 0;
data->j = w - 1;
data->f = 0;
start = clock();
hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_2, (void*)data, 0, &threadID);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
end = clock();
time = (double)(end - start) / CLOCKS_PER_SEC;
time1[4] = time;
cout << "\n多线程快速+归并排序1.0time: " << time;
//Write("./多线程线程快排+归并1.0.txt");
/*多线程快速+归并2.0*/
readfile();
data = new QckSortThread;
data->p = num;
data->i = 0;
data->j = w - 1;
data->f = 0;
start = clock();
hThread= (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)data, 0, &threadID);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
end = clock();
time = (double)(end - start) / CLOCKS_PER_SEC;
time1[5] = time;
cout << "\n多线程快速+归并排序2.0time: " << time << endl;
// Write("./多线程快排+归并2.0.txt");
cout << "快排加速比:" << (time1[1] - time1[0]) / time1[1] << endl;
cout << "归并加速比:" << (time1[3] - time1[2]) / time1[3] << endl;
cout << "快+归1.0加速比" << (time1[1] - time1[4]) / time1[1] << endl;
cout << "快+归2.0加速比" << (time1[1] - time1[5]) / time1[1] << endl;
cout << level << endl;
level++;
}
return 0;
}