记录一次使用pthread编程实现并行快速排序程序过程。
一、快排
快速排序算法时间复杂度为O(nlogn),流程如下:
① 从数组序列中选出一个元素,称为中间值(pivot)
② 把数组序列分成两个子序列,比中间值小的元素放在一个子序列,比中间值大的元素放
在另一个子序列。该操作称为分割(partition)
③ 递归地对两个子序列分别重复上述操作,直到子序列中元素个数为1
二、程序设计
1.串行程序
观察快排程序流程,可以发现创建子序列存放数组两个部分等价于将需要交换位置的元素在原数组中进行交换,这样可以节省空间,其程序可写为:
#include <stdio.h>
#include <math.h>
#define N 20
void quik_sort(double *num,int left,int right){
if (right<=left){
return;
}
int start=left,end=right;
double mid=0;
while (left<right){
while (left<right and num[right]>=num[start]){
right-=1;
}
while (left<right and num[left]<=num[start]){
left+=1;
}
mid=num[left];
num[left]=num[right];
num[right]=mid;
}
mid=num[start];
num[start]=num[left];
num[left]=mid;
quik_sort(num,start,left-1);
quik_sort(num,left+1,end);
}
int main(){
double num[N];
srand(1);//保持一致性
for(int i=0;i<N;i++){
num[i]=(double)rand()/RAND_MAX;
}
for(int i=0;i<N;i++){
printf("%lf\n",num[i]);
}
quik_sort(num,0,N-1);
for(int i=0;i<N;i++){
printf("later:%lf\n",num[i]);
}
return 0;
}
这种情况下,我们可以发现该算法其实分为两步,第一步为找到中间值所在位置索引,第二步为递归地为两边的数组进行快排,由于第二步所需子数组由第一步确定,所以这两步不能并行,那我们的并行化思路有三种:
若我们对第一步进行并行,将寻找需要交换的两个元素分给两个线程执行,在需要大量交换操作时,这种方法用于管理线程的开销可能会导致其不如串行程序;
若我们对第二步进行并行,将两个递归操作交给两个线程来完成,那么我们将需要创建规模为O(logn)个线程用于递归操作,导致我们需要使用mutex对线程数量进行控制。
若我们对数据进行并行,将数组分为线程数量个数的子数组,每个线程负责一块的快速排序,最后再将这几块有序数组按归并排序的方法在O(nlogn)的时间内组合起来,但这就需要额外的空间存放数组。
这里实现第二、三种,第一种并行效率太低了所以不考虑。
2.并行程序(c++实现)
第二种:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
#include "ctime"
#include "chrono"
#include <iostream>
#define N 200000000
using namespace std;
using namespace std::chrono;
double num[N];
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
int count=1;
struct my_para{
int left;
int right;
};
void *quik_sort(void*arg){
struct my_para *para;
para=(struct my_para*)arg;
int left=(*para).left;
int right=(*para).right;
if (right<=left){
return NULL;
}
int start=left,end=right;
double mid=0;
while (left<right){
while (left<right && num[right]>=num[start]){
right-=1;
}
while (left<right && num[left]<=num[start]){
left+=1;
}
mid=num[left];
num[left]=num[right];
num[right]=mid;
}
if(num[left]>num[start]){
left-=1;
}
mid=num[start];
num[start]=num[left];
num[left]=mid;
pthread_t pid;
bool createT=false;
pthread_mutex_lock(&lock);
if(count>0){
count--;
createT=true;
}
pthread_mutex_unlock(&lock);
struct my_para s_left;
s_left.left=start;
s_left.right=left-1;
if(createT){
pthread_create(&pid,NULL,quik_sort,&s_left);
}else{
quik_sort(&s_left);
}
struct my_para s_right;
s_right.left=left+1;
s_right.right=end;
quik_sort(&s_right);
if(createT){
pthread_join(pid,NULL);
pthread_mutex_lock(&lock);
count++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
double my(){
struct my_para para;
para.left = 0;
para.right=N-1;
srand(1);//保持一致性
for(int i=0;i<N;i++){
num[i]=(double)rand()/RAND_MAX;
}
// Start measuring time
auto start = system_clock::now();
quik_sort(¶);
// Stop measuring time and calculate the elapsed time
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
for(int i=1;i<N;i++){
if(num[i]<num[i-1])printf("wrong:%lf,idx:%d\n",num[i],i);
}
return double(duration.count()) * microseconds::period::num / microseconds::period::den;
}
int main(int argc,char *argv[])
{
if(argc==1){
count=1;
}else if(argc==2){
count=(int)*argv[1];
count-=48;
}
double elapsed = 0;
for(int i=0;i<10;i++){
elapsed+=my();
}
printf("用时: %.3f 秒\n", elapsed/10);
return 0;
}
第三种:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
#include "ctime"
#include "chrono"
#include <iostream>
#define N 2000*100000
using namespace std;
using namespace std::chrono;
double *num;
int count=1;
struct my_para{
int left;
int right;
};
void quik_sort(int start,int end){
if (end<=start){
return;
}
int left = start+1,right = end;
double mid=0;
while (left<right){
while (left<right && num[right]>=num[start]){
right-=1;
}
while (left<right && num[left]<num[start]){
left+=1;
}
// swap(num[left], num[right]);
mid=num[left];
num[left]=num[right];
num[right]=mid;
}
if(num[left]>num[start]){
left-=1;
}
// swap(num[left], num[start]);
mid=num[start];
num[start]=num[left];
num[left]=mid;
quik_sort(start,left-1);
quik_sort(left+1,end);
return;
}
void *my_pthread(void*arg){
struct my_para *para;
para=(struct my_para*)arg;
int left=(*para).left;
int right=(*para).right;
quik_sort(left,right);
pthread_exit(NULL);
}
double my(){
struct my_para *para = (struct my_para*)malloc(sizeof(struct my_para)*count);
if(para==NULL){
printf("para");
return 0;
}
double *sortedNum = (double*)malloc(sizeof(double)*N);
if(sortedNum==NULL){
printf("sortedNum");
return 0;
}
for(int i=0;i<N;i++){
num[i]=(double)rand();
}
pthread_t pid[count];
int numThread=N/count;
auto start = system_clock::now();
for(int i=0;i<count;i++){
if(i==count-1){
para[i].left = i*numThread;
para[i].right=N-1;
}else{
para[i].left = i*numThread;
para[i].right=(i+1)*numThread-1;
}
int status = pthread_create(&pid[i],NULL,my_pthread,¶[i]);
if (status != 0) {
printf("create thread error");
return -1;
}
}
int ind[count];
for(int i=0;i<count;i++){
pthread_join(pid[i],NULL);
ind[i]=para[i].left;
}
double min_num=RAND_MAX;
int mid_ind=0;
for(int i=0;i<N;i++){
min_num=RAND_MAX;
for(int j=0;j<count;j++){
if(ind[j]<=para[j].right){
if(num[ind[j]]<min_num){
min_num=num[ind[j]];
mid_ind=j;
}
}
}
sortedNum[i]=min_num;
ind[mid_ind]+=1;
}
// Stop measuring time and calculate the elapsed time
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
for(int i=1;i<N;i++){//check if sort worked?
if(sortedNum[i]<sortedNum[i-1])printf("wrong:%lf,idx:%d\n",sortedNum[i],i);
// printf("ab?");
}
free(sortedNum);
free(para);
return double(duration.count()) * microseconds::period::num / microseconds::period::den;
}
int main(int argc,char *argv[])
{
if(argc==1){
count=1;
}else if(argc==2){
count=(int)*argv[1];
count-=48;
}
srand(1);//保持一致性
num = (double*)malloc(sizeof(double)*N);
if(num==NULL){
printf("num");
return -1;
}
double elapsed = 0;
for(int i=0;i<10;i++){
elapsed+=my();
}
printf("用时: %.3f 秒\n", elapsed/10);
free(num);
return 0;
}
PS:
遇到了segmentation fault,就注释函数一个个查找原因,我这边是电脑内存不够,溢出了导致的,重启电脑就行。