实验三:线程的互斥
2.3.1实验⽬的
(1)熟练掌握Windows系统环境下线程的创建与撤销。
(2)熟悉Windows系统提供的线程互斥API。
(3)使⽤Windows系统提供的线程互斥API解决实际问题。
2.3.2 实验准备知识:相关API函数介绍
2.3.2.1临界区对象
临界区对象(CriticalSection)包括初始化临界区(InitializeCriticalSection())、进⼊临界区
( EnterCriticalSection ())、退出临界区( LeaveCriticalSection ())及删除临界区
(DeleteCriticalSection())等API函数。
初始化临界区
InitializeCriticalSection()⽤于初始化临界区对象。
原型:
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
参数说明:
lpCriticalSection:指出临界区对象的地址。
返回值:
该函数没有返回值。
⽤法举例:
LPCRITCAL_SECTION hCriticalSection;
CRITICAL_SECTION Critical;
hCriticalSection=&Critical;
InitializeCriticalSection(hCriticalSection);
进⼊临界区
EnterCriticalSection()等待进⼊临界区的权限,当获得该权限后进⼊临界区。
原型:
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
参数说明:
lpCriticalSection:指出临界区对象的地址。
返回值:
该函数没有返回值。
⽤法举例:
LPCRITICAL_SECTION hCriticalSection;
CRITICAL_SECTION Critical;
hCriticalSection=&Critical;
EnterCriticalSection(hCriticalSection);
退出临界区
LeaveCriticalSection()释放临界区的使⽤权限。
原型:
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection );
参数说明:
lpCriticalSection:指出临界区对象的地址。
返回值:
该函数没有返回值
⽤法举例:
LPCRITICAL_SECTION hCriticalSection;
CRITICAL_SECTION Critical;
hCriticalSection=&Critical;
LeaveCriticalSection(hCriticalSection);
删除临界区
DeleteCriticalSection()删除与临界区有关的所有系统资源。
原型:
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
参数说明:
lpCriticalSection:指出临界区对象的地址。
返回值:
该函数没有返回值。
⽤法举例:
LPCRITICAL_SECTION hCriticalSection;
CRITICAL_SECTION Critical;
hCriticalSection=&Critical;
DeleteCriticalSection(hCriticalSection);
2.3.2.2互斥对象
互斥对象(Mutex)包括创建互斥对象(CreateMutex())、打开互斥对象(OpenMutex())及释
放互斥对象(ReleaseMutex())API函数。
创建互斥对象
CreateMutex(0⽤于创建⼀个互斥对象。
原型:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexASributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
参数说明:
lpMutexASributes:指定安全属性,为NULL时,信号量得到⼀个,默认的安全描述符。
bInitialOwner:指定初始的互斥对象。如果该值为TRUE并且互斥对象已经纯在,则调⽤线程获得互斥
对象的所有权,否则调⽤线程不能获得互斥对象的所有权。想要知道互斥对象是否已经存在,参⻅返回
值说明。
lpName:给出互斥对象的名字。
返回值:
互斥对象创建成功,将返回该互斥对象的句柄。如果给出的互斥对象是系统已经存在的互斥对象,将返回这个已存在互斥对象的句柄。如果失败,系统返回NULL,可以调⽤函数GetLastError()查询失败的
原因。
⽤法举例:
static HANDLE hHandle1=NULL;
//常⻅⼀个名为"MutexName1"的互斥对象
hHandle1=CreateMutex(NULL,FALSE, “MutexName1”);
打开互斥对象
OpenMutex()⽤于打开⼀个互斥对象。
原型:
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
参数说明:
指明系统安全属性⽀持的对互斥对象所有可能的访问。如果系统安全属性不⽀持,则不能获得对互
斥对象访问权。
(1)dwDesireAccess:指出发开后要对互斥对象进⾏何种访问,具体描述如表2-4所示。
表 2-4 对互斥对象的访问种类
访问 描述
MUTEX_ALL_ACCESS 可以进⾏对任何互斥对象的访问
SYNCHRONIZE 使⽤等待函数 wait functions 等待互斥对象成为可⽤状态或使⽤
ReleaseMutex()释放使⽤权,从⽽获得互斥对象的使⽤权
(2) bInheritHandle: 指出返回信号量的句柄是否可以继承。
(3) IpName : 给出信号量的名字。
返回值
互斥对象打开成功,将返回该互斥对象的句柄;如果失败,系统返回NULL,可以调⽤函数
GetLastError()查询失败的原因。
⽤法举例:
static HANDLE hHandle1 = NULL;
//打开⼀个名为"MutexName1"的互斥对象
hHandle=OpenMutex(SYNCHRONIZE,NULL,“MutexName1”);
- 释放互斥对象
ReleaseMutex() ⽤于释放互斥对象。
原型:
BOOL ReleaseMutex(
HANDLE hMUTEX;
);
参数说明:
hMutex:Mutex对象的句柄。CreateMutex()和OpenMutex()函数返回该句柄。
返回值:如果成功,将返回⼀个⾮0值;如果失败系统将返回0,可以调⽤函数 GetLastError()查询失败的原因。
⽤法举例:
sta2c HANDLE hHandle1=NULL;
BOOL rc;
rc= ReleaseMutex(hHandle1)
2.3.3 实验内容
完成两个⼦线程之间的互斥。在主线程中使⽤系统调⽤ CreateThread()创建两个⼦线程,并使两个⼦
线程互斥的使⽤全局变量 count。
2.3.4 实验要求
能正确的使⽤临界区对象,包括初始化临界区 Ini2alizeCri2calSec2on()、进⼊临界区
EnterCri2calSec2on()、退出临界区 LeaveCri2cal()及删除临界区 DeleteCri2calSec2on() 进⼀步理解线程
的互斥。
2.3.5 实验指导
具体操作过程同实验⼀,在 Microsoa Visual C++ 6.0 环境下建⽴⼀个MFC⽀持的控制台⼯程⽂件,编
写C程序,在主线程中使⽤Ini2alizeCri2calSec2on()初始化临界区,然后建⽴两个⼦线程,在两个⼦线程
中使⽤全局变量 count 的前、后分别使⽤EnterCri2calSea2on()进⼊临界区LeaveCri2calSec2on()退出临
界区,等两个⼦线程运⾏完毕,主线程使⽤DeleteCri2calSec2on()删除临界区并撤销线程。
2.3.6 实验总结
该试验完成了两个⼦线程之间的互斥。若去掉互斥对象,观察全局变量count的变化,了解互斥对象
的作⽤,进⼀步理解线程的互斥。本试验也可以使⽤互斥对象(Mutex)来完成两个线程的互斥,互斥对
象(Mutex)的使⽤⽅法与信号量对象相似,这⾥不再说明,请同学⾃⼰完成。线程互斥访问全局变量
count
如图 2-5所示。
2.3.7 源程序
#include "stdafx.h"
#include <Windows.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[ ]=__FILE__;
#endif
//
//The one and only applica2on object
CWinApp theApp;
using namespace std;
static int count =5;
static HANDLE h1;
static HANDLE h2;
LPCRITICAL_SECTION hCriticalSection; //定义指向临界区对象的地址指针
CRITICAL_SECTION Critical; //定义临界区
void func1();
void func2();
int _tmain(int argc,TCHAR*argv[],TCHAR*envp[])
{int nRetCode=0; DWORD dwThreadID1, dwThreadID2;
hCriticalSection=&Critical; //将指向临界区对象的指针指向临界区
InitializeCriticalSection(hCriticalSection); //初始化临界区
h1=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)func1,
(LPVOID)NULL,
0,&dwThreadID1); //创建线程 func1
if (h1==NULL) printf("Thread1 create FAIL!\n");
else printf("Thread1 create Success!\n");
h2=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,
(LPTHREAD_START_ROUTINE)func2,
(LPVOID)NULL,
0,&dwThreadID2); //创建线程 func2
if (h2==NULL) printf("Thread2 create FAIL!\n");
else printf("Thread2 create Success!\n");
Sleep(1000);
CloseHandle(h1);
CloseHandle(h2);
DeleteCriticalSection(hCriticalSection); //删除临界区
ExitThread(0);
return nRetCode;
}
void func2()
{ int r2;
EnterCriticalSection (hCriticalSection); //进?临界区
r2=count;
_sleep(100);
r2=r2+1;
count=r2;
printf("count in func2=%d\n",count);
LeaveCriticalSection (hCriticalSection); //退出临界区
}
void func1()
{ int r1;
EnterCriticalSection (hCriticalSection); //进?临界区
r1=count;
_sleep(500);
r1=r1+1;
count=r1;
printf("count in func1=%d\n",count);
LeaveCriticalSection (hCriticalSection); //退出临界区
}
2.3.8 实验展望
上⾯的实验是使⽤临界区对象(Cri2calSec2on)实现的,同学们可以使⽤互斥对象(Mutex)来完
成。
在完成以上三个实验后,同学们对Windows系统提供的线程创建与撤销、线程的同步与互斥、API有
了⼀定的了解,在此基础上设计并完成⼀个综合性的实验解决实际同步与互斥问题,如⽣产者与消费者问题、读者与写者问题等。饰演的题⽬可以⾃⾏设计,但要求必须涉及线程的创建与撤销、等待对象函
数(waitFunc2ons)、信号量对象(Semaphore)临界区对象(Cri2calSec2on)或互斥对象(Mutex)的使
⽤。