Windows程序程序设计_第1页
Windows程序程序设计_第2页
Windows程序程序设计_第3页
Windows程序程序设计_第4页
Windows程序程序设计_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Windows程序程序设计…线程同步3.2.5互斥内核对象互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解,可参照图3.8给出的互斥内核对象的工作模型:图3.8使用互斥内核对象对共享资源的保护图(a)中的箭头为要访问资源(矩形框)的线程,但只有第二个线程拥有互斥对象(黑点)并得以进入到共享资源,而其他线程则会被排斥在外(如图(b)所示)。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出(如图(c)所示),其他任何一个试图访问此资源的线程都有机会得到此互斥对象。以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex、OpenMutex、ReleaseMutex、WaitForSingleObject和WaitForMultipleObjects等。在使用互斥对象前,首先要通过CreateMutex或OpenMutex创建或打开一个互斥对象。CreateMutex函数原型如下:HANDLECreateMutex(LPSECURITY_ATTRIBUTESlpMutexAttributes,//安全属性指针BOOLbInitialOwner, //初始拥有者LPCTSTRlpName //互斥对象名);参数blnitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex函数得到此互斥对象的句柄。OpenMutex函数原型为:HANDLEOpenMutex(DWORDdwDesiredAccess,//访问标志BOOLbInheritHandle,//继承标志LPCTSTRlpName//互斥对象名);当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex函数来释放其拥有的互斥对象,其函数原型为:BOOLReleaseMutex(HANDLEhMutex);其惟一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject和WaitForMultipleObjects等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_O(对于WaitForSingleObject函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects函数),而是将返回一个WAIT_ABANDONED_O(对于WaitForSingleObject函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects函数),以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面是示例代码://互斥对象HANDLEhMutex=NULL;charg_cArray[10];UINTThreadProc1(LPVOIDpParam){//等待互斥对象通知WaitForSingleObject(hMutex,INFINITE);//对共享资源进行写入操作for(inti=0;i<10;i++)g_cArray[i]='a';Sleep(1);}//释放互斥对象ReleaseMutex(hMutex);return0;}UINTThreadProc2(LPVOIDpParam){//等待互斥对象通知WaitForSingleObject(hMutex,INFINITE);//对共享资源进行写入操作for(inti=0;i<10;i++){g_cArray[10-i-1]='b';Sleep(1);}//释放互斥对象ReleaseMutex(hMutex);return0;}线程的使用使程序处理能够更加灵活,而这种灵活同样也会带来各种不确定性的可能。尤其是在多个线程对同一公共变量进行访问时。虽然未使用线程同步的程序代码在逻辑上或许没有什么问题,但为了确保程序的正确、可靠运行,必须在适当的场合采取线程同步措施。3.2.6线程局部存储

线程局部存储(thread-localstorage,TLS)是一个使用很方便的存储线程局部数据的系统。利用TLS机制可以为进程中所有的线程关联若干个数据,各个线程通过由TLS分配的全局索引来访问与自己关联的数据。这样,每个线程都可以有线程局部的静态存储数据。用于管理TLS的数据结构是很简单的,Windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间,如图3.9所示。IlUMII时■TL5.MriMUMAVALABLE2InJeiTLSklENfdUMAVALAQLEHIrtfeiTLS AVIlUMII时■TL5.MriMUMAVALABLE2InJeiTLSklENfdUMAVALAQLEHIrtfeiTLS AV施5LE1图3.9TSL机制在内部使用的数据结构000IrUti100IWi200IrxlrES打丄iPd*40■—J1曲・0lhM-2运行在系统中的每一个进程都有图3.9所示的一个位数组。位数组的成员是一个标志,每个标志的值被设为FREE或INUSE,指示了此标志对应的数组索引是否在使用中。Windodws保证至少有TLS_MINIMUM_AVAILABLE(定义在WinNT.h文件中)个标志位可用。动态使用TLS的典型步骤如下。(1)主线程调用TlsAlloc函数为线程局部存储分配索引,函数原型为:DWORDTlsAlloc(void);//返回一个TLS索引如上所述,系统为每一个进程都维护着一个长度为TLS_MINIMUM_AVAILABLE的位数组,TlsAlloc的返回值就是数组的一个下标(索引)。这个位数组的惟一用途就是记忆哪一个下标在使用中。初始状态下,此位数组成员的值都是FREE,表示未被使用。当调用TlsAlloc的时候,系统会挨个检查这个数组中成员的值,直到找到一个值为FREE的成员。把找到的成员的值由FREE改为INUSE后,TlsAlloc函数返回该成员的索引。如果不能找到一个值为FREE的成员,TlsAlloc函数就返回TLS_OUT_OF_INDEXES(在WinBase.h文件中定义为一1),意味着失败。例如,在第一次调用TlsAlloc的时候,系统发现位数组中第一个成员的值是FREE,它就将此成员的值改为INUSE,然后返回0。当一个线程被创建时,Windows就会在进程地址空间中为该线程分配一个长度为TLS_MINIMUM_AVAILABLE的数组,数组成员的值都被初始化为0。在内部,系统将此数组与该线程关联起来,保证只能在该线程中访问此数组中的数据。如图3.7所示,每个线程都有它自己的数组,数组成员可以存储任何数据。每个线程调用TlsSetValue和TlsGetValue设置或读取线程数组中的值,函数原型为:BOOLTlsSetValue(DWORDdwTlsIndex,//TLS索引LPVOIDlpTlsValue //要设置的值);LPVOIDTlsGetValue(DWORDdwTlsIndex);//TLS索引TlsSetValue函数将参数lpTlsValue指定的值放入索引为dwTlsIndex的线程数组成员中。这样,lpTlsValue的值就与调用TlsSetValue函数的线程关联了起来。此函数调用成功,会返回TRUE。调用TlsSetValue函数,一个线程只能改变自己线程数组中成员的值,而没有办法为另一个线程设置TLS值。到现在为止,将数据从一个线程传到另一个线程的惟一方法是在创建线程时使用线程函数的参数。TlsGetValue函数的作用是取得线程数组中索引为dwTlsIndex的成员的值。TlsSetValue和TlsGetValue分别用于设置和取得线程数组中的特定成员的值,而它们使用的索引就是TlsAlloc函数的返回值。这就充分说明了进程中惟一的位数组和各线程数组的关系。例如,TlsAlloc返回3,那就说明索引3被此进程中的每一个正在运行的和以后要被创建的线程保存起来,用以访问各自线程数组中对应的成员的值。主线程调用TlsFree释放局部存储索引。函数的惟一参数是TlsAlloc返回的索引。利用TLS可以给特定的线程关联一个数据。比如下面的例子将每个线程的创建时间与该线程关联了起来,这样,在线程终止的时候就可以得到线程的生命周期。整个跟踪线程运行时间的例子的代码如下:#include<stdio.h> //03UseTLS工程下#include<windows.h>#include<process.h>//利用TLS跟踪线程的运行时间DWORDg_tlsUsedTime;voidInitStartTime();DWORDGetUsedTime();UINT__stdcallThreadFunc(LPVOID){inti;//初始化开始时间InitStartTime();//模拟长时间工作i=10000*10000;while(i--){}//打印出本线程运行的时间printf("Thisthreadiscomingtoend.ThreadID:%-5d,UsedTime:%d\n",::GetCurrentThreadId(),GetUsedTime());return0;}intmain(intargc,char*argv[]){UINTuId;inti;HANDLEh[10];//通过在进程位数组中申请一个索引,初始化线程运行时间记录系统g_tlsUsedTime=::TlsAlloc();//令十个线程同时运行,并等待它们各自的输出结果for(i=0;i<10;i++){ h[i]=(HANDLE)::_beginthreadex(NULL,0,ThreadFunc,NULL,0,&uId);}for(i=0;i<10;i++){::WaitForSingleObject(h[i],INFINITE);::CloseHandle(h[i]);}//通过释放线程局部存储索引,释放时间记录系统占用的资源::TlsFree(g_tlsUsedTime);return0;}//初始化线程的开始时间voidInitStartTime(){//获得当前时间,将线程的创建时间与线程对象相关联DWORDdwStart=::GetTickCount();::TlsSetValue(g_tlsUsedTime,(LPVOID)dwStart);}//取得一个线程已经运行的时间DWORDGetUsedTime(){//获得当前时间,返回当前时间和线程创建时间的差值DWORDdwElapsed=::GetTickCoun

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论