网络编程技术实验2Windows线程同步和互斥解析.docx
- 文档编号:23684107
- 上传时间:2023-05-19
- 格式:DOCX
- 页数:16
- 大小:21.34KB
网络编程技术实验2Windows线程同步和互斥解析.docx
《网络编程技术实验2Windows线程同步和互斥解析.docx》由会员分享,可在线阅读,更多相关《网络编程技术实验2Windows线程同步和互斥解析.docx(16页珍藏版)》请在冰豆网上搜索。
网络编程技术实验2Windows线程同步和互斥解析
实验2Windows线程同步和互斥
实验目的
1、了解Windows内核对线程同步的支持。
2、了解C的线程函数库及Windows基本的线程API函数的使用。
3、进一步理解线程的同步控制原理。
预备知识
一、Windows线程同步机制(注:
互斥是同步的一种特例)
⏹事件(Event)
⏹临界区(CriticalSection)
⏹互斥量(Mutex)
⏹信号量(Semaphore)
1、是否能跨进程使用?
互斥量、信号量、事件都可以跨进程来实现同步数据操作。
临界区只能用在同一进程的线程间互斥,因为临界区无名(无句柄)。
如果只为了在进程内部用的话,使用临界区会带来速度上的优势并能够减少资源占用量。
2、其它区别
临界区:
访问临界资源的代码段。
课堂上讲过。
(存钱、取钱的例子还记得吗?
)
互斥量:
资源独占使用
信号量:
资源计数器
事件对象:
可以通过“通知”的方式来保持线程的同步。
事件是WIN32中最灵活的线程间同步机制。
事件存在两种状态:
激发状态(SignaledorTrue)未激发状态(UnsignaledorFalse)。
3、详细解释:
(见下面实验内容每个程序前)
二、VC++(略)
实验内容
1、用事件(Event)对象来进行线程同步
⏹事件可分为两类:
⏹手动设置:
这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。
⏹自动恢复:
一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。
⏹_beginthread函数:
创建一个线程。
所在库文件:
#include
uintptr_t_beginthread(
void(*start_address)(void*),
unsignedstack_size,
void*arglist
);
返回值:
假如成功,函数将返回一个处理信息对这个新创建的线程。
如果失败_beginthread将返回-1。
start_address
新线程的起始地址,指向新线程调用的函数的起始地址stack_sizestack_size新线程的堆栈大小,可以为0arglistarglist传递给线程的参数列表,无参数是为NULL
⏹CreateEvent函数:
创建事件对象windows.h
HANDLECreateEvent(
//SECURITY_ATTRIBUTES结构指针,可为NULL
LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset, //手动/自动
//TRUE:
在WaitForSingleObject后必须手动调用ResetEvent清除信号
//FALSE:
在WaitForSingleObject后,系统自动清除事件信号
BOOLbInitialState,//初始状态
LPCTSTRlpName//事件的名称
);
⏹使用“事件”机制应注意以下事项:
⏹如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突;
⏹事件是否要自动恢复;
⏹事件的初始状态设置。
⏹由于event对象属于内核对象,故进程B可以调用OpenEvent函数通过对象的名字获得进程A中event对象的句柄,然后将这个句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函数中。
此法可以实现一个进程的线程控制另一进程中线程的运行,例如:
HANDLEhEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
ResetEvent(hEvent);
验证程序:
3个线程。
主线程创建2个线程。
一读,一写。
写线程(并不真写,只是输出writing等字符串)完成后,读线程才能读,读线程完成后,主线程才能结束。
新建一个Win32控制台应用程序项目(win32consoleapplication)
#include"stdafx.h"
#include
#include
#include
#include
HANDLEevRead,evFinish;//全局变量,事件对象的句柄
voidReadThread(LPVOIDparam){
WaitForSingleObject(evRead,INFINITE);//等待evRead被激活
cout<<"Reading...读完成,唤醒主线程"< SetEvent(evFinish);//激活evFinish事件 } voidWriteThread(LPVOIDparam){ cout<<"Writing...写完成,唤醒读线程"< SetEvent(evRead);//激活evRead事件 } intmain(intargc,char*argv[]) { evRead=CreateEvent(NULL,FALSE,FALSE,NULL); evFinish=CreateEvent(NULL,FALSE,FALSE,NULL); _beginthread(ReadThread,0,NULL); _beginthread(WriteThread,0,NULL); WaitForSingleObject(evFinish,INFINITE);//等待evFinish被激活 cout<<"End."< return0; } 如果引入了 errorC2065: '_beginthread': undeclaredidentifier Errorexecutingcl.exe. 解决: 工程à设置àc/c++标签à分类(Category)下拉列表里选择代码生成(CodeGeneration)à选用运行时库(UseRun-TimeLibrary)下拉列表里选择多线程Multithreaded。 然后重新编译。 若还不行,再选MultithreadedDLL。 验证: 用//将两条WaitForSingleObject语句屏蔽。 重新编译运行,多运行几次,看结果有何不同。 思考原因。 2、用临界区(CriticalSection)来进行线程互斥 ⏹临界区是保证在某一时刻只有一个线程能访问数据的简便办法。 在任意时刻只允许一个线程对共享资源进行访问。 ⏹如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。 临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 ⏹临界区包含两个操作原语: ⏹EnterCriticalSection()进入临界区 ⏹LeaveCriticalSection()离开临界区 ⏹EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。 否则,临界区保护的共享资源将永远不会被释放。 ⏹虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。 ⏹创建临界区 为了创建临界区,首先必须在进程中分配一个全局CRITICAL_SECTION数据结构: CRITICAL_SECTIONgCriticalSection; ⏹使用临界区 使用临界区之前,必须调用InitializeCriticalSection函数初始化: VOIDInitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); ⏹进入临界区 调用EnterCriticalSection函数进入临界区: VOIDEnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); ⏹离开临界区 调用LeaveCriticalSection函数退出了临界区: VOIDLeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); ⏹删除临界区 调用DeleteCriticalSection函数删除临界区: VOIDDeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); ⏹临界区一般用法: EnterCriticalSection(&gCriticalSection); //dosomething LeaveCriticalSection(&gCriticalSection); ⏹关于临界区的使用,有下列注意点: ⏹每个共享资源使用一个CRITICAL_SECTION变量; ⏹不要长时间运行关键代码段,当一个关键代码段长时间运行时,其他线程就会进入等待状态,这会降低应用程序的运行性能; ⏹如果需要同时访问多个资源,则可能连续调用EnterCriticalSection; ⏹CriticalSection不是OS核心对象,如果进入临界区的线程"挂"了,将无法释放临界资源。 这个缺点在Mutex中得到了弥补。 验证程序: 一个银行系统中两个线程对同一账户执行取款操作,余额1000元。 一个使用ATM机取900元,另一个使用存折在柜台取700元。 如果不加于控制,会使得账户余额为负数。 #include"stdafx.h" #include #include #include #include inttotal=1000; HANDLEevFin[2]; CRITICAL_SECTIONcs; voidWithDrawThread1(LPVOIDparam){//取900元 EnterCriticalSection(&cs); if((total-900)>=0){ total-=900; cout<<"你取了900元"< }else{ cout<<"钱不够了,禁止取钱,马上退卡! "< } LeaveCriticalSection(&cs); SetEvent(evFin[0]); } voidWithDrawThread2(LPVOIDparam){//取700元 EnterCriticalSection(&cs); if((total-700)>=0){ total-=700; cout<<"你取了700元"< }else{ cout<<"钱不够了,禁止取钱! "< } LeaveCriticalSection(&cs); SetEvent(evFin[1]); } intmain(intargc,char*argv[]) { evFin[0]=CreateEvent(NULL,FALSE,FALSE,NULL); evFin[1]=CreateEvent(NULL,FALSE,FALSE,NULL); InitializeCriticalSection(&cs); _beginthread(WithDrawThread1,0,NULL); _beginthread(WithDrawThread2,0,NULL); WaitForMultipleObjects(2,evFin,TRUE,INFINITE); DeleteCriticalSection(&cs); cout<<"余额是"< return0; } 多运行几次,观察结果并分析。 3、用互斥量(Mutex)来进行线程互斥 ⏹互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限。 ⏹由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。 ⏹当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。 ⏹互斥量比临界区复杂。 因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。 用CreateMutex函数创建互斥量: HANDLECreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes,//安全属性结构指针,可为NULL BOOLbInitialOwner, //是否占有该互斥量,TRUE: 占有,FALSE: 不占有 LPCTSTRlpName //信号量的名称 ); 涉及到的其它API如下: 打开一个互斥量: OpenMutex函数: HANDLEOpenMutex( DWORDfdwAccess,//值为SYNCHRONIZE或MUTEX_ALL_ACCESS BOOLfInherit, LPTSTRlpszName); 释放一个互斥量: ReleaseMutex函数: BOOLReleaseMutex(HANDLEhMutex); 该函数将互斥量从无信号状态变到有信号状态。 互斥和临界区的比较如下图: 特性 互斥 临界区 运行速度 慢 快 是否能够跨进程边界来使用 是 否 声明 HANDLEhmtx; CRITICAL_SECTIONcs; 初始化 hmtx=CreateMutex(NULL,FALSE,NULL); InitializeCriticalSection(&es); 清除 CloseHandle(hmtx); DeleteCriticalSection(&cs); 无限等待 WaitForSingleObject(hmtx,INFINITE); EnterCriticalSection(&cs); 0等待 WaitForSingleObject(hmtx,0); TryEnterCriticalSection(&cs); 任意等待 WaitForSingleObject(hmtx,dwMilliseconds); 不能 释放 ReleaseMutex(hmtx); LeaveCriticalSection(&cs); 是否能够等待其他内核对象 是(使用WaitForMultipleObjects或类似的函数) 否 验证程序: 主线程创建3个线程。 它们各自要输出一些字符串。 #include"stdafx.h" #include #include #defineTHREAD_INSTANCE_NUMBER3//主线程要创建3个线程 LONGg_RessourceInUse=FALSE; LONGg_iCounter=0; voidThreadProc(void*pData){ intThreadNumTemp=(*(int*)pData); HANDLEhMutex; cout<<"ThreadProc: "< "< if((hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"Mutex.Test"))==NULL) cout<<"Openmutexerror! "< WaitForSingleObject(hMutex,INFINITE); cout<<"ThreadProc: "< ReleaseMutex(hMutex); CloseHandle(hMutex); cout<<"ThreadProc: "< } intmain(intargc,char*argv[]) { DWORDID[THREAD_INSTANCE_NUMBER];//数组,存放3个线程的ID号码(CreateThread返回) HANDLEh[THREAD_INSTANCE_NUMBER];//数组,存放3个线程的句柄 HANDLEhMutex;//互斥量的句柄 inti; if((hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"Mutex.Test"))==NULL){ if((hMutex=CreateMutex(NULL,FALSE,"Mutex.Test"))==NULL){ cout<<"CreateMutexerror! "< return0; } } for(i=0;i { h[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,(void*)&ID[i],0,&(ID[i])); if(h[i]==NULL){ cout<<"创建线程失败! "< }else{ cout<<"创建线程成功"< } } WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,h,true,INFINITE); cout<<"Closethemutexhandle! "< return0; } 思考: 输出多样,哪里体现了互斥量的作用? 4、用信号量(Semaphore)来实现线程同步 ⏹信号量是维护0到指定最大值之间的同步对象。 ⏹信号量状态在其计数大于0时是有信号的,而其计数是0时是无信号的。 ⏹信号量对象在控制上可以支持有限数量共享资源的访问。 ⏹创建信号量: HANDLECreateSemaphore( PSECURITY_ATTRIBUTEpsa, LONGlInitialCount,//开始时可供使用的资源数 LONGlMaximumCount,//最大资源数 PCTSTRpszName); ⏹撤销信号量: BOOLWINAPIReleaseSemaphore( HANDLEhSemaphore, LONGlReleaseCount,//信号量的当前资源数增加lReleaseCount LPLONGlpPreviousCount ); ⏹打开信号量 HANDLEOpenSemaphore( DWORDfdwAccess, BOOLbInherithandle, PCTSTRpszName ); 验证程序: 生产者与消费者问题。 #include"stdafx.h" #include #include #defineTHREAD_INSTANCE_NUMBER3 #definePRODUCT_NUMBER2 #defineMAX_ITEMS2 //定义信号量 HANDLEm_S_Full;//Semaphore HANDLEm_S_Empty;//Semaphore HANDLEm_E_Mutex;//Event intcounter=0; voidThreadProducer(void*pData){//生产者 intj; intThreadNumTemp=(*(int*)pData); for(j=0;j WaitForSingleObject(m_S_Empty,INFINITE); WaitForSingleObject(m_E_Mutex,INFINITE); //OKnow,putproduct counter++; cout<<"ThreadProducer: "< cout<<"ThreadProducer: "< //relasecomsumer’ssemaphore ReleaseSemaphore(m_S_Full,1,NULL); //seteventtosignal SetEvent(m_E_Mutex); } } voidThreadConsumer(void*pData){//消费者 intj; intThreadNumTemp=(*(int*)pData); for(j=0;j WaitForSingleObject(m_S_Full,INFINITE); WaitForSingleObject(m_E_Mutex,INFINITE); //OKnow,getaproduct counter--; cout<<"ThreadConsumer: "< cout<<"ThreadConsumer: "< //relaseproducer’ssemaphore Releas
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 网络 编程 技术 实验 Windows 线程 同步 解析