多核多线程技术WINAPI实验报告1.docx
《多核多线程技术WINAPI实验报告1.docx》由会员分享,可在线阅读,更多相关《多核多线程技术WINAPI实验报告1.docx(14页珍藏版)》请在冰点文库上搜索。
![多核多线程技术WINAPI实验报告1.docx](https://file1.bingdoc.com/fileroot1/2023-5/4/4fc423c6-65a7-4bee-9d8b-603f50ec35e6/4fc423c6-65a7-4bee-9d8b-603f50ec35e61.gif)
多核多线程技术WINAPI实验报告1
实验一:
Windows*Threads多线程编程
模块一:
基础练习
4编译执行,输出结果:
HelloThread1
HelloThread3
HelloThread0
HelloThread2
简答与思考:
1写出修改后的HelloThreads的代码。
关键代码:
(已用黄色标记)
//HelloWorld.cpp:
定义控制台应用程序的入口点.
#include"stdafx.h"
#include
constintnumThreads=4;
DWORDWINAPIhelloFunc(LPVOIDarg){
intmyNum=*((int*)arg);
printf("HelloThread%d\n",myNum);
return0;
}
int_tmain(intargc,_TCHAR*argv[])
{
HANDLEhThread[numThreads];
inttNum[numThreads];
for(inti=0;i{
tNum[i]=i;
hThread[i]=CreateThread(NULL,0,helloFunc,&tNum[i],0,NULL);
}
WaitForMultipleObjects(numThreads,hThread,true,INFINITE);
return0;
}
2实验总结
在主程序中循环调用CreateThread()函数生成四个子线程分别去执行helloFunc()函数,并将线程的句柄保存在hTread数组中,CreateThread()的第四个参数给每个线程传递参数tNum获取该线程号,由helloFunc()的参数接收并指向内容。
由于主线程为每个子线程传递了不同的参数,所以通过子线程的执行结果可以判断出四个线程的执行顺序。
这个实验要我们初步认识到了程序的并行化设计思想,为以后的并行化程序设计打下了基础。
模块二:
临界区实验
3串行程序编译执行,Pi的值为:
Pi=3.141592654
ThetimetocalculatePIwas0.327000seconds
6并行程序编译执行,Pi的值为:
PI=3.1419305356
ThetimetocalculatePIwas0.063000seconds
简答与思考:
1如何进行并行化的?
请写出并行化的思路与具体的代码。
本例中用两个线程协同工作,分别计算局部的sum1然后累加到全局变量sum中,由于多线程多sum变量的修改会引起数据竞争,因此在这里采用了临界区方法,将对sum的更新操作定义为临界区操作。
依据刚获得的本线程序号,以步进长度numthreads(线程总数)间隔地累加求和,好比线程0求和第0、2、4、6、8……块,线程1求和第1、3、5、7、9……块。
这是为保证两个线程负载平衡。
for(inti=myNum;i{
x=(i+0.5)*step;
multhread+=4.0/(1.0+x*x);
}
这部分在多线程并行部分之外,是由单线程执行的,作用是将前述各个线程独立间隔求和的结果汇总,得到完整的和,也就是数值积分结果。
关键代码:
(已用黄色标记)
//CriticalSectionPi.cpp:
定义控制台应用程序的入口点
#include"stdafx.h"
#include
#include
constintnumthreads=2;
staticlongnum_steps=10000000;
doublestep=0.0;
doublepi=0.0,sum=0.0;
CRITICAL_SECTIONcs;
DWORDWINAPIFunc(LPVOIDarg)
{
intmyNum=*((int*)arg);
doublesum1=1.0,x;
for(inti=myNum;i{
x=(i+0.5)*step;
sum1+=4.0/(1.0+x*x);
}
EnterCriticalSection(&cs);
sum+=sum1;
LeaveCriticalSection(&cs);
return0;
}
int_tmain(intargc,_TCHAR*argv[])
{
clock_tstart,stop;
start=clock();
HANDLEhthread[numthreads];
inttNum[numthreads];
InitializeCriticalSection(&cs);
step=1.0/(double)num_steps;
for(inti=0;i{
tNum[i]=i;
hthread[i]=CreateThread(NULL,0,Func,&tNum[i],0,NULL);
}
WaitForMultipleObjects(numthreads,hthread,true,INFINITE);
DeleteCriticalSection(&cs);
pi=step*sum;
stop=clock();
printf("Pi=%12.9f\n",pi);
printf("Thetimeofcalculationwas%0.12fseconds\n",((double)(stop-start)/1000.0));
return0;
}
2在本实验中,哪些变量是需要保护的?
采取什么方法实现的?
将sum1累加到sum中,sum是临界区代码,需要保护,因为其是引起数据竞争的关键;采用把其设置成为临界区代码的方法实现。
EnterCriticalSection(&cs);
sum+=sum1;
LeaveCriticalSection(&cs);
3是否可以对该并行化方案进行进一步的优化?
如何优化?
暂时没想到其他进一步优化方案。
4实验总结
并行程序相对于串行程序的加速比为5.1904,相比之下并行效率提高了2.6倍,这是我们想要的结果,并行的确是提高了计算的速度,所以对于这种大数据累加的问题对算法的优化是必要的。
模块三:
事件实验
3编译执行,Resultis0.69314218
Thetimeofcalculationwas21.544000seconds
4阅读代码,回答下面问题。
(1)主线程共创建5个子线程。
(2)各子线程调用的函数各是什么?
DWORDWINAPIthreadProc(LPVOIDpar){}
DWORDWINAPImasterThreadProc(LPVOIDpar){}
(3)主线程等待5个子线程的执行结束。
6改进后的,编译执行,Resultis0.69314218
Thetimeofcalculationwas15.807000seconds
简答与思考:
1在WINAPIthreadProc(LPVOIDpar){}函数中为什么用临界区互斥了线程对threadCount的访问?
为什么对于全局数据变量sums的访问没有互斥?
WINAPIthreadProc(LPVOIDpar){}函数使用多线程执行的threadCount是记录线程个数的,而它对于masterThreadProc()函数要等待所有线程(threadCount)做完对sums的计算,然后再进行最后的四个线程结构的累加,它是这两个函数的共享变量,对于它的操作必须原子化,以保证每次只有一个线程对其自增,所以使用临界区互斥了每个线程对threadCount的访问,避免了数据冲突的发生。
而对于数据变量sums每一个数组元素下标值是每个子线程获得的参数,该参数标记了各个子线程,sums数组分别对应一个线程,从而使每个子线程操作的变量分别保存在对应的数组元素中,四个线程互不影响,所以并不需要互斥。
2简述源代码中存在的问题,详述提出的改进方案及相关代码。
源代码中为使“master”子线程等待其余四个子线程执行完毕,使用空循环保持“master”子进程的“等待”状态,这显然不是好的方法;改进方案中使用了事件,事件用于线条线程间的执行顺序以保证对共享资源操作的完整性,本程序中,“master”子线程是另外创建的,它需要另外四个子线程的执行结果,所以需要等待以保证获得它们的结果后再进行操作,使用事件机制,程序中定义四个未激发的人工重置事件,“master”子进程在执行时以wait方式等待事件被激发,由于其余四个子线程在完成任务后将事件从未激发态设置为激发态,从而使“master”子线程继续执行余下操作。
关键代码:
(以用黄色标记)
//ThreadEvent.cpp:
定义控制台应用程序的入口点
//麦凯特尔对数级数估算ln(1+x),(-1#include"stdafx.h"
#include
#include
#include
#include
#defineNUMTHREADS4
#defineSERIES_MEMBER_COUNT100000
HANDLE*threadHandles,masterThreadHandle,*eventHandles;
CRITICAL_SECTIONcountCS;
double*sums;
doublex=1.0,res=0.0;
intthreadCount=0;
doublegetMember(intn,doublex)
{
doublenumerator=1;
for(inti=0;inumerator=numerator*x;
if(n%2==0)
return(-numerator/n);
else
returnnumerator/n;
}
DWORDWINAPIthreadProc(LPVOIDpar)
{
intthreadIndex=*((int*)par);
sums[threadIndex]=0;
for(inti=threadIndex;isums[threadIndex]+=getMember(i+1,x);
SetEvent(eventHandles[threadIndex]);
//EnterCriticalSection(&countCS);
//threadCount++;
//LeaveCriticalSection(&countCS);
deletepar;
return0;
}
DWORDWINAPImasterThreadProc(LPVOIDpar)
{
for(inti=0;iResumeThread(threadHandles[i]);
//Startcomputingthreads
//while(threadCount!
=NUMTHREADS)//busywaituntilallthreadsaredonewithcomputationofpartialsums
//{}
WaitForMultipleObjects(NUMTHREADS,eventHandles,TRUE,INFINITE);
res=0;
for(inti=0;ires+=sums[i];
return0;
}
int_tmain(intargc,_TCHAR*argv[])
{
clock_tstart,stop;
threadHandles=newHANDLE[NUMTHREADS+1];
eventHandles=newHANDLE[NUMTHREADS+1];
//InitializeCriticalSection(&countCS);
sums=newdouble[NUMTHREADS];
start=clock();
for(inti=0;i{
int*threadIdPtr=newint;
*threadIdPtr=i;
threadHandles[i]=CreateThread(NULL,0,threadProc,threadIdPtr,CREATE_SUSPENDED,NULL);
eventHandles[i]=CreateEvent(NULL,TRUE,FALSE,NULL);
}
threadHandles[NUMTHREADS]=CreateThread(NULL,0,masterThreadProc,NULL,0,NULL);
printf("Countofln(1+x)Mercator'sseriesmembersis%d\n",SERIES_MEMBER_COUNT);
printf("Argumentvalueofxis%f\n",(double)x);
WaitForMultipleObjects(NUMTHREADS+1,threadHandles,TRUE,INFINITE);
stop=clock();
for(inti=0;iCloseHandle(threadHandles[i]);
deletethreadHandles;
deleteeventHandles;
//DeleteCriticalSection(&countCS);
deletesums;
printf("Resultis%10.8f\n",res);
printf("Byfunctioncallln(1+%f)=%10.8f\n",x,log(1+x));
printf("Thetimeofcalculationwas%fseconds\n",((double)(stop-start)/1000.0));
printf("Pressanykey...");
getch();
return0;
}
3是否可以对该并行化方案进行进一步的优化?
如何优化?
线程在被创建时就执行,不用再去唤醒,“master”只需等待事件被激发,效率会有所提高。
4实验总结
模块四:
信号量实验
3确定串行版本项目为启动项,编译执行。
TotalWords:
____984719__________
TotalEvenWords:
___494315_______
TotalOddWords:
____490404_______
4确定并行版本项目为启动项,编译执行。
TotalWords:
___984719___________
TotalEvenWords:
__494306________
TotalOddWords:
___490404________
9修正后项目的输出结果为:
TotalWords:
____984719__________
TotalEvenWords:
__494315________
TotalOddWords:
___490404________
简答与思考:
1Serial.cpp与Threaded.cpp代码执行结果不一致的原因是什么?
在多线程中fd和TotalWords,TotalEvenWords,TotalOddWords属于共享变量,在并发执行的过程中会造成数据冲突。
fd对于每个线程是互斥的,是因为在文件指针往下一行改变时,不允许其他线程对该操作有影响,不然就会造成该问题的计数结果不正确的现象;而TotalWords,TotalEvenWords,TotalOddWords这几个变量,是计算总的字符串个数,含有偶数数量字符的字符串的个数以及含有奇数数量字符的字符串的个数。
毋庸置疑,它们也是共享资源,在对他们进行累加时,要注意数据冲突。
2如何修改Threaded.cpp代码?
写出修改思路和关键代码。
前面我们已经学过了用临界区可以对于共享资源进行线程的互斥访问,这个问题也可以采取临界区去做,但是我们试着尝试另一种方法----信号量。
信号量也是一种内核对象,它可以对当前资源计数,这也是它与临界区最大的不同,当资源数量大于0时,等待该信号量的线程就可以获得该资源得以继续执行。
本问题的改进方案是:
在代码中应用两个信号量hSem1,hSem2,hSem1用于线程对文件指针fd的互斥,hSem2则是用于对全局变量TotalWords,TotalEvenWords,TotalOddWords的互斥。
对于每一个子线程,由于我们的思路是按行计算,然后累加的,对偶数数量字符的字符串的个数以及含有奇数数量字符的字符串的个数的计算,要注意采取巧妙点的处理办法。
关键代码:
(已用黄色标记)
#include
#include
#include
FILE*fd;
intTotalEvenWords=0,TotalOddWords=0,TotalWords=0;
HANDLEhSem1,hSem2;
constintNUMTHREADS=4;
intGetNextLine(FILE*f,char*Line)
{
if(fgets(Line,132,f)==NULL)
if(feof(f))
returnEOF;
else
return1;
}
intGetWordAndLetterCount(char*Line)
{
intWord_Count=0,OddWords=0,EvenWords=0,Letter_Count=0;
for(inti=0;i<132;i++)
{
if((Line[i]!
='')&&(Line[i]!
=0)&&(Line[i]!
='\n'))Letter_Count++;
else{
if(Letter_Count!
=0){
if(Letter_Count%2){
OddWords++;
Word_Count++;
Letter_Count=0;
}
else{
EvenWords++;
Word_Count++;
Letter_Count=0;
}
}
if(Line[i]==0)break;
}
}
return(Word_Count*10000+OddWords*100+EvenWords);
}
DWORDWINAPICountWords(LPVOIDarg)
{
BOOLbDone=FALSE;
charinLine[132];
inttemp=0;
while(!
bDone)
{
WaitForSingleObject(hSem1,INFINITE);
bDone=(GetNextLine(fd,inLine)==EOF);
ReleaseSemaphore(hSem1,1,NULL);
if(!
bDone){
temp=GetWordAndLetterCount(inLine);
WaitForSingleObject(hSem2,INFINITE);
TotalWords+=temp/10000;
TotalOddWords+=(temp%10000)/100;
TotalEvenWords+=temp%100;
ReleaseSemaphore(hSem2,1,NULL);
}
}
return0;
}
intmain()
{
HANDLEhThread[NUMTHREADS];
hSem1=CreateSemaphore(NULL,1,1,NULL);
hSem2=CreateSemaphore(NULL,1,1,NULL);
fd=fopen("InFile1.txt","r");//Openfileforread
for(inti=0;ihThread[i]=CreateThread(NULL,0,CountWords,NULL,0,NULL);
WaitForMultipleObjects(NUMTHREADS,hThread,TRUE,INFINITE);
fclose(fd);
printf("TotalWords=%8d\n\n",TotalWords);
printf("TotalOddWords=%7d\nTotalEvenWords=%7d\n",TotalOddWords,TotalEvenWords);
getchar();
return0;
}
3是否还有更优的修改方案?
为什么?
暂时没想到其他进一步优化方案。
4实验总结
信号量这种内核对象,常被用于对于有限数据空间是访问控制、对一段代码的线程访问数量的控制、对有限资源的访问控制这几个方面。
信号量这种给资源计数的思想在很多方面都会用到,但是使用起来也要谨慎,用不好说不定会给程序带来潜在的死锁隐患。