用多线程加速快排(方式二)

如题,这是方式二,方式一在我的另一篇博客-《用多线程加速快排(方式一)》

方式二可能会有点难理解,它首先要求深入理解快排,在此基础上对其改造(引入一个”任务池“,是多线程共享的。它用栈来表示。这个“任务池”的应用是程序的核心!);再接着还要用peterson算法实现多线程的并发控制(我操作系统课上学的是peterson算法用来进行进程并发控制,我觉得也可以用来对线程并发控制,后面也实现了)。这个版本的多线程快排比较麻烦,且并发的控制会带来一定消耗。但它比我另一篇博客写的多线程版快排的方式一(方式一好理解得多)会适用范围更广:因为它可以扩展到两个个处理器以上的计算机(而方式一只能用于双核)。其次,这里用到的“任务池”的思想,我觉得更有推广意义。

先说说我的想法是怎么来的:

我们都知道在经典快排的quicksort()中,有两个步骤:一是用partition()来将数据分成两部分,前一部分都小于某元素,后一部分都大于某元素。二是用两个quicksort()分别处理步骤一的两个两个任务。也就是说,经典快排结构如下:

void quicksort(int i,int j)

{

if(i<j)

{

partition(...)

quicksort(....);

quicksort(....);

}

}

站得远一点看上面的结构,那就是先用partition将大任务得到两个小任务,然后调用quicksort处理这两个小任务,但是 仔细想想,下面的quicksort处理的非得是刚刚partition得到的两个子任务不可吗?仔细想想,是不需要的。认识到这一点有什么用呢?看下面。

再走远一点来看:将partition看成是任务的"制造者“,而quicksort(0看成是任务的”消费者“(“执行者”)。在经典快排中,是先制造出的任务,然后立刻将其”消费“(”完成“)。而我的改造是将任务存到“任务池”(用栈实现,每次取任务时取栈顶的任务),然后再从任务池去取任务,注意,在单线程时,A线程刚刚网“任务池”里放入任务,它接着执行从“任务池”中取任务,由于任务池是用栈实现的,所以它取得一定是刚刚放进去的任务(放进栈顶,也从栈顶取),执行流程是:A线程往“任务池”放任务->A线程从“任务池”取任务。但是在多线程时,执行流程可能这样:A线程往“任务池”放任务->A线程往“任务池”放任务->A程从“任务池”取任务!也就是说, 所以A线程取得不一定是刚刚放进去的任务(放进栈顶,也从栈顶取)了!注意,这样程序也不会跑乱,因为上面说了“quicksort处理的非得是刚刚partition得到的两个子任务不可吗?仔细想想,是不需要的”。

我将程序结构改成了这样:

void quicksort(int i,int j)

{

if(i<j)

{

partition(...)//得到两个任务

XXXX//将这两个任务放在一个存放待完成任务的栈里;

XXXX//从栈顶中取出任务,(在单线程时,这取出的一定是上一步刚放进去的两个任务中后放进去的那个,但是,注意,在多线程时就不一定了!)

quicksort(....);

}

else//说明任务执行完毕,在经典快排中,是通过回溯到上一层的quicksort()函数,。在通过这个修改后,就不用再返回,而是执行完任务,直接从“任务池”里找一个新任务来执行!

{

XXXX//从栈顶中取出任务,(在单线程时,这取出的一定是上一步刚放进去的两个任务中后放进去的那个,但是,注意,在多线程时就不一定了!)

quicksort(....);

}

}


多次运行(在我的双核处理器计算机上)(运行时查看资源管理器,CPU利用率是100%),运行时间是:

2425

2462

2422

2494

经典快排(即单线程)(运行时查看资源管理器,CPU利用率是50%左右)处理同样的数据量的运行时间为:

3788

3701

4378

3625

理论上,多线程版在我的双核处理器上,应该可以节省一半的运行时间,但事实上,没有节省那么多,那估计是由于用peterson算法同步有一定消耗。

总结:这个多线程版的快排通过引入一个栈来当做“任务池”,继而通过多线程将每个处理器封装成一个独立的执行个体,每个个体(处理器)不断从“任务池”中取任务,执行完,再“取”。这样就充分发挥了多核的优势。这个思想:“任务池”+线程封装处理器,我觉得是最重要的,因为它可以很方便地扩展到更多核(理论上无上限),而当计算机中有有更多处理器时,理论上,有几个处理器,就提高几倍,(但要考虑到同步机制带来的消耗,所以不可能达到理论上的那种水品)。(写的我肚子都饿了...)可能有些地方说的不太清楚,有时间的话我会再修改修改。如果不清楚程序,留言,我们一起讨论。不说了,吃午饭去鸟!

多线程版快排源代码如下:

#include<iostream>
#include<time.h>
#include<windows.h>
#include<process.h>
using namespace std;

int first_thread_p,first_thread_r;
int thread_end=0;
boolean flag[2];//Peterson算法要用到的变量
int turn;//Peterson算法要用到的变量
int pool[1000];//用来存待完成的作业,每2个一对,作为函数quicksort()的参数,本程序一开始该“池子”里就只有一个任务:计算quicksort(0,999),
//那么就有pool[0]=0,pool[1]=999这一对来记录要完成的任务。主线程和子线程完成了取出的任务,就再从这个池子中取出task标记的任务,继续完成。
int task=0;//标记池子里的亟待做的任务。通过作为pool的下标的方式来标记。
//由于pool是每两个作为一对(quicksort()有两个形参,需要两个作为一对),所以pool中每添加一个任务,就会存储两个参数,故task会加2,不是加1.


int a[1000]={
1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,534
,1,3,20,43,2,7,4,87,34,65,12,77,11,34,6,7,8,53,2,23,199,55,43,4,34,76,433,6,4,3,2,55,6,3,66,32,5,67,32,2,6,66,7,8,7,9,0,8,7,62,3,4,5,6,7,9,3,4,5,6,2,45,6,7,98,5,34,2,4,6,78,9,67,59,64,22,4,5,60,70,89,61,4,322,23,226,744,8,6,65,533,422,344,264,233,367,434,444,55,20
};

void quicksort(int p,int r,int thr_id);


int partition(int p,int r)
{
int x=a[r];
int t;
int i=p-1;
for(int j=p;j<=r-1;j++)
if(a[j]<=x)
{
i++;
t=a[i];
a[i]=a[j];
a[j]=t;
}
for(int i1=0;i1<1000;i1++)
for(int j1=0;j1<1000;j1++)
{
}
t=a[i+1];
a[i+1]=a[r];
a[r]=t;
return i+1;
}

unsigned __stdcall  quicksort_thread(void *)
{
clock_t start, end;
double time;
int p=first_thread_p;
int r=first_thread_r;

// cout<<" 进入线程1...";
// start=clock();
if(p<r) 
{
cout<<"p is : "<<p<<" r is : "<<r<<endl;
cout<<" go to quicksort..."<<endl;
// int q = partition(p,r);
quicksort(p,r,1);
// cout<<" 线程...";
// quicksort(q+1,r);
}
cout<<" complete quicksort...";
// end=clock();
// time=(double)(end-start);
// cout<<"the thread time is : "<<time<<endl;
thread_end=1;
return 0;
}

void quicksort(int p,int r,int thr_id)//不管p和r是多少,当它们作为参数传给这个函数时,
//p和r下标(简称p,r下标对)确定的区间一定是没有执行过quicksort()的。因为p,r下标对是从“任务池”里取出的,而任务池里
//p,r下标对(作为一个整体看待)都是等待执行的。
{
// cout<<"进入quicksort,...线程id=: "<<thr_id<<endl;
// system("pause");
bool no_other=false;
int kk=1,can,q;
int o_thr_id;
if(thr_id==0)//若thr_id==0,表示为主线程,那么另一个线程(o_thr_id)是子线程。
o_thr_id=1;
else//若thr_id==1,表示为子线程,那么另一个线程(o_thr_id)是主线程。
o_thr_id=1;


if(p<r) 
{
q = partition(p,r);
//临界区1
flag[thr_id]=true;
turn=thr_id;
while(flag[o_thr_id]&&(turn==thr_id))
{
}

pool[task]=p;
pool[task+1]=q-1;
task=task+2;
pool[task]=q+1;
pool[task+1]=r;
task=task+2;
flag[thr_id]=false;
flag[thr_id]=true; //实现线程互斥
turn=thr_id; //实现线程互斥
while(flag[o_thr_id]&&(turn==thr_id)) //实现线程互斥
{
}
can=0;
if(task!=0) //注意,1,检测任务数要放在临界区里   2,别放在下面的else语句后,那样当本来task=2,减2操作后就变为了0,本应还执行最后一行quicksort(p,q,thr_id)的,结果,由于检测task==0,break掉了.
{
can=1;
p=pool[task-2];
q=pool[task-1];
task=task-2;

}
// cout<<"退出临界区...线程id=: "<<thr_id<<endl;
flag[thr_id]=false; //实现线程互斥
//临界区2结束
if(can==1)
quicksort(p,q,thr_id);

}
else
{

// cout<<"刚做完了最小的一个任务,开始找任务...线程id=: "<<thr_id<<endl;
flag[thr_id]=true; //实现线程互斥
turn=thr_id; //实现线程互斥
while(flag[o_thr_id]&&(turn==thr_id)) //实现线程互斥
{
}
can=0;
if(task!=0) //注意,1,检测任务数要放在临界区里   2,别放在下面的else语句后,那样当本来task=2,减2操作后就变为了0,本应还执行最后一行quicksort(p,q,thr_id)的,结果,由于检测task==0,break掉了.
{
// cout<<"it is over...线程id=: "<<thr_id<<endl;
//flag[thr_id]=false;//注意退出时必须让出这个标记,好让别的线程进去。

can=1;
p=pool[task-2];
q=pool[task-1];
task=task-2;
}

// cout<<"退出临界区...线程id=: "<<thr_id<<endl;
flag[thr_id]=false; //实现线程互斥


//临界区2结束
if(can==1)
quicksort(p,q,thr_id);
}
}

int main()
{
// for(int i=0;i<1000;i++)
// b[i]=-1;//初始化任务池,注意每相邻两个是一个p,r下标对,如b[0]和b[1]分别记录待执行void quicksort(int p,int r)中的p和r下标,
//b[2]和b[3]也分别记录待执行的void quicksort(int p,int r)中的p和r下标... 
//全初始化为-1表示任务池里一开始没任务。
HANDLE hThread2;
unsigned int threadID1;
clock_t start, end,t1,end1;
int j=0,k=0;
double total_time,time1;
flag[0]=flag[1]=false;

start=clock();
t1=partition(0,999);
cout<<"t is "<<t1<<endl;
first_thread_p=0; first_thread_r=t1-1;
hThread2 = (HANDLE)_beginthreadex(NULL, 0, &quicksort_thread, NULL, 0, &threadID1);
quicksort(t1+1,999,0);
// quicksort(0,999,0);
end1=clock();
time1=(double)(end1-start);
cout<<"main thread is over"<<"time1 is :"<<time1<<endl;
// while(thread_end==0)
// { }
end=clock();
total_time=(double)(end-start);
cout<<"total  time is : "<<total_time<<endl;
for(int r=0;r<100;r++)
cout<<a[r]<<endl;
return 0;
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值