**
操作系统实验二——线程的同步
**
一、 实验目的
(1)进一步掌握windows系统环境下线程的创建和撤销。
(2)熟悉windows系统提供的线程同步API(是WINDOWS提供给应用程序与操作系统的接口)。
(3)使用windows系统提供的线程同步API解决实际问题。
二、 实验准备
1)WaitForMultipleObject() //用于等待一个对象。
他等待的对象可以为以下对象之一:
1、Change notification:变化通知。
2、Console input:控制台输入。
3、Events:事件。
4、Job:作业。
5、Mutex:互斥信号量。
6、Process:进程。
7、Semaphore:计数信号量。
8、Thread:线程。
9、Waitable timer:定时器。
原型:
DWORD WaitForSingleObject(
HANDLE hHandle, //对象句柄DWORD
dwMilliseconds //等待时间
);
参数说明:
hHandle:等待对象的对象句柄。该对象句柄必须为SYNCHRONIZE([ˈsɪŋkrənaɪz],同步)访问。
dwMilliseconds:等待时间,单位为ms。若改值为0,函数在测试对象的状态后立即返回,若为INFINITE(无限的),函数一直等待下去,直到收到一个信号将其唤醒。
返回值:如果返回成功,其返回值说明是何种事件导致函数返回。
用法举例:
Staitic HANDLE hHandle1=NULL;
DWORD dRes;
dRes=WaitForSingleObject(hHandle1, 10); //等待对象的句柄为hHandle,等待时间为1000ms。
2)WaitForMultipleObject() //等待多个对象
在指定时间内等待多个对象,他等待的对象与WaitForSingleObject()相同。
原型:
DWORD WaitForMultipleObject(
DWORD nCount, //句柄数组中的句柄数XONST
HANDLE *lpHandles, //指向对象句柄数组的指针BOOL
fWaitAll, //等待类型DWORD
dwMilliseconds //等待时间
};
参数说明:
nCount:由 指 针*lpHandles指 定 的 句 柄 数 组 中 的 句 柄 数 , 最 大 数 是MAXIMUM_WAIT_OBJECTS。
*lpHandles:指向对象句柄数组的指针。
fWAitAll:等待类型。若存为true,当由lpHandles数组指定的所有对象被唤醒时函数返回;若为FALSE,当由lpHandles数组制定的某一个对象被唤醒时函数返回,且有返回值说明事由哪个对象引起的函数返回。
dwMilliseconds :等待时间。单位为ms。若该值为0,函数测试对象的状态后立即返回;若为INFINITE,函数一直等待下去,直到收到一个信号将其唤醒。
返回值:如果成功返回,其返回值说明是何种事件导致函数返回。
3)信号量对象(semaphore)包括创建信号量(CreateSemaphore())打开信号量OpenSemaphore()及增加信号量的值(ReleaseSemaphore())API函数。
CreateSemaphore() //用于创建一个信号量。
原型:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全属性LONG
lInitialCount, //信号量对象初始值LONG
liMaximumCount, //信号量最大值LPCTSTR
lpName//信号量名
);
参数说明:
lpSemaphoreAttributes:指定安全属性,为null是,信号量得到一个默认的安全描述符。
lInitialCount:指定信号量对象的初始值。该值必须大于等于0,小于等于lMaximumCount 。当其值大于0是,信号量被唤醒。当该函数释放了一个等待该信号量的线程时,lInitialCount值减1,当调用函数ReleaseSemaphore()时,按其指定的数量加一个值。
lMaximumCount:指出该信号量的最大值,该值必须大于0.
lpName:给出该信号量的名字。
返回值:信号量创建成功,将返回该型号量的句柄。如果给出的信号量名是系统已经存在的信号量,将返回这个已经存在的信号量的句柄。如果失败,系统返回null,还可以调用函数GEtLastError()查询失败的原因。
用法举例:
Static HANDLE hHandle1=null;//创建一个信号量,其初值为0,最大值为5,信号量的名字为“SemphoreName1”hHnadle1= CreateSemaphore(NULL,0,5,"SemphoreName1");
OpenSemaphore() //用于打开一个信号量。
原型:
HANDLEOpenSemaphore(
DWORDdwDesidedAccess, //访问标志BOOL
bInheritHandle, //继承标志 LPCTSTR
lpNme //信号量名
);
参数说明:
(1)dwDesiredAccess:指出打开后要对信号量进行何种访问。
(2)bInheritHandle:指出返回的的信号量句柄是否可以继承。
(3)lpName:给出信号量的名字
返回值:信号量打开成功,将返回信号量的句柄;如果失败,系统返回null,可以调用函数GetLastError()查询失败的原因。
用法举例:
Static HANDLE hHandle1=null;//打开一个名为 ” SemphoreName1 ” 的信号量,之后可使用ReleaseSemaphore()函数增加信号量的值hHandle1=OpenSemaphore(SEMAPHORE_MOFDIFY_START,NULL, ”SemphoreName1” );
ReleaseSemaphore() //用于增加信号量的值。
原型:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, //信号量对象句柄LONG
lReleaseCount, //信号量要增加数值LPLONG
lpPreiousCount //信号量要增加数值地址
);
参数说明:
(1).hSemaphore:创建或打开信号量时给出的信号量对象句柄。Windows NT中建议使用SEMAPHORE_MODIFY_STARTE访问属性打开该信号量。
(2). lReleaseCount:信号量要增加数值。该值必须大于0。如果增加该值后大于信号创建时给出的lMaximumCount值,则增加操作失效,函数返回FALSE。
(3). lpPreiousCount :接收信号量的一个32位的一个变量。若不需要接受该值,可以指定为null。返回值: 如果成功,将返回一个非0值;如果失败,系统返回一个0,可以调用一个GetLastError()查询失败的原因。
用法举例:
Static HANDLE hHandle1=NULL;
BOOL rc;
rc= ReleaseSemaphore(hHandle1,1,NULL);//给信号量的值加1;
三、 实验内容
完成主子两个线程之间的同步,要求子线程先执 行。在主线程中使用系统调用CreateThread()创建一个子线程。主线程创建一个子线程后进入阻塞状态,直到子线程运行完毕后唤醒主线程。
实验代码:
(1)等待一个对象:
using namespace std;
static HANDLE hHandle1=NULL;
void chef(){
printf("馒头开始制作,预计等待时间5秒。\n");
Sleep(5000);
printf("馒头制作完成!\n");
BOOL rc;
DWORD err;
rc=ReleaseSemaphore(hHandle1,1,NULL);
err=GetLastError(); //子线程唤醒主线程
printf("ReleaseSemaphore err=%d\n",err);
if(rc==0){
printf("Semaphore Release Fail!\n");
}
else{
printf("Semaphore Release Success!rc=%d\n",rc);
}
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
DWORD dRes,err;
hHandle1=CreateSemaphore(NULL,0,1,"SemaphoreName1");//创建一个信号量
if(hHandle1==NULL){
printf("Semaphore Create Fail!\n");
}
else{
printf("Semaphore Create Success!\n");
}
hHandle1=OpenSemaphore(SYNCHRONIZE|SEMAPHORE_MODIFY_STATE,
NULL,
"SemaphoreName1"); //打开一个信号量
if(hHandle1==NULL){
printf("Semaphore Open Fail!\n");
}
else{
printf("Semaphore Open Success!\n");
}
HANDLE handle1=NULL;
DWORD ThreadID1=NULL;
handle1=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)chef,
(LPVOID)NULL,
0,
&ThreadID1);
dRes=WaitForSingleObject(hHandle1,INFINITE);
err=GetLastError();
if(err==0){
printf("馒头上菜完毕,请开动吧.\n");
}
else{
printf("WaitForSingleObject err=%d\n",err);
if(dRes==WAIT_TIMEOUT) printf("TIMEOUT!dRes=%d\n",dRes);
else if(dRes==WAIT_OBJECT_0) printf("WAIT_OBJECT!dRes=%d\n",dRes);
else if(dRes==WAIT_ABANDONED) printf("WAIT_ABANDONED!Dres=%d\n",dRes);
else printf("dRes=%d\n",dRes);
}
return nRetCode;
}
(2)等待多个对象
using namespace std;
void chef(int meal_code){
if(meal_code==0){
Sleep(5000);
printf("馒头制作完成!\n");
}
else if(meal_code==1){
Sleep(3000);
printf("米饭制作完成!\n");
}
else if(meal_code==2){
Sleep(9000);
printf("馄饨制作完成!\n");
}
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
DWORD dRes,err;
HANDLE handle1=NULL;
HANDLE handle2=NULL;
HANDLE handle3=NULL;
DWORD ThreadID1=NULL;
DWORD ThreadID2=NULL;
DWORD ThreadID3=NULL;
int a=0;
int b=1;
int c=2;
handle1=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)chef,
(LPVOID)a,
0,
&ThreadID1);
handle2=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)chef,
(LPVOID)b,
0,
&ThreadID2);
handle3=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)chef,
(LPVOID)c,
0,
&ThreadID3);
HANDLE hHandles[3];
hHandles[0]=handle1;
hHandles[1]=handle2;
hHandles[2]=handle3;
dRes=WaitForMultipleObjects(3,hHandles,1,INFINITE);
err=GetLastError();
if(err==0){
printf("所有菜品上菜完毕,请开动吧.\n");
}
else{
printf("WaitForMultipleObjects err=%d\n",err);
if(dRes==WAIT_TIMEOUT) printf("TIMEOUT!dRes=%d\n",dRes);
else if(dRes==WAIT_OBJECT_0) printf("WAIT_OBJECT!dRes=%d\n",dRes);
else if(dRes==WAIT_ABANDONED) printf("WAIT_ABANDONED!Dres=%d\n",dRes);
else printf("dRes=%d\n",dRes);
}
return nRetCode;
}
四、实验结果与总结
实验完成了主、子线程的同步,主线程创建子线程后,主线程塞,让子线程先执行,等子线程执行完后由子线程唤醒子线程。主、子线程运行情况如图:
米饭的等待时间为3秒,馒头的等待时间为5秒,馄饨的等待时间为9秒,所以米饭先打印完毕,馄饨最后打印完毕。
将WaitForMultipleObjects的返回类型改为0时,某一个对象被唤醒时函数即返回,且有返回值说明事由哪个对象引起的函数返回。