Windows程序的执行单元_第1页
Windows程序的执行单元_第2页
Windows程序的执行单元_第3页
Windows程序的执行单元_第4页
Windows程序的执行单元_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

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

文档简介

第3章Windows程序的执行单元——线程Windows程序的执行单元共52页,您现在浏览的是第1页!3.1多线程线程的创建线程内核对象线程的终止线程的优先级C/C++运行期库Windows程序的执行单元共52页,您现在浏览的是第2页!线程的创建多线程并发执行,os为其轮流分配cpu时间片一般情况,主线程接受用户输入,显示运行结果;新创建的线程来处理长时间的操作,如读写文件、访问网络等,这样即便程序忙于繁重的工作也可以由专门的线程响应用户命令每个线程必须拥有入口点函数,主线程的为main。辅助线程入口点函数称为线程函数,定义如下:DWORDWINAPIThreadProc(LPVOIDlpParam);

函数名称ThreadProc可以是任意的Windows程序的执行单元共52页,您现在浏览的是第3页!线程的创建创建新线程用CreateThreadHANDLE

CreateThread(

LPSECURITY_ATTRIBUTES

lpsa,DWORD

cbStack,LPTHREAD_START_ROUTINE

lpStartAddr,LPVOID

lpvThreadParam,DWORD

fdwCreate, LPDWORD

lpIDThread

);

Windows程序的执行单元共52页,您现在浏览的是第4页!WaitForSingleObjectDWORD

WaitForSingleObject(HANDLE

hHandle,DWORD

dwMilliseconds

);

Thisfunctionreturnswhenthespecifiedobjectisinthesignaledstate(受信状态)orwhenthetime-outintervalelapses.该函数用于等待指定的对象(hHandle)变成受信状态。 说明:在例03ThreadDemo工程中,用于等待辅助线程对应的函数ThreadProc()执行完成。调用该函数将阻塞主线程。一个可执行对象有两种状态:未受信、受信。线程对象只有当线程结束才为受信态。Windows程序的执行单元共52页,您现在浏览的是第5页!线程的创建例子程序Windows程序的执行单元共52页,您现在浏览的是第6页!2.线程创建例子中使用计数调用CreateThread后,使用计数=2,线程函数返回,系统将使使用计数减少1,即使用计数=1,接下来又调用CloseHandle使使用计数减1,即最后使用计数=0,该内存空间被收回。Windows程序的执行单元共52页,您现在浏览的是第7页!3.1.3线程的终止线程终止的4种方法线程函数自然退出ExitThreadTerminateThread造成无法作清除工作,ExitProcess不提倡例子程序02ExitThreadWindows程序的执行单元共52页,您现在浏览的是第8页!THREAD_PRIORITY_TIME_CRITICAL实时THREAD_PRIORITY_HIGHEST最高THREAD_PRIORITY_ABOVE_NORMAL高于正常THREAD_PRIORITY_NORMAL正常THREAD_PRIORITY_BELOW_NORMAL低于正常THREAD_PRIORITY_LOWEST最低THREAD_PRIORITY_ABOVE_IDLE高于最低THREAD_PRIORITY_IDLE空闲Windows程序的执行单元共52页,您现在浏览的是第9页!3.1.5C/C++运行期库在实际的开发过程中,一般使用c/c++运行期函数_beginthreadx代替CreateThread函数。_beginthreadx首先申请一些用于线程同步的变量,然后调用CreateThreadVc++默认的c/c++运行期库不支持_beginthreadx,需要设置”Project/settings/c/c++/codegeneration/…./选中multithreadDLL”即可。需要#include<process.h>同样,使用_endthreadex代替ExitThread。该函数首先释放用于线程同步的变量,再调用ExitThreadWindows程序的执行单元共52页,您现在浏览的是第10页!3.2线程同步解决同步问题的方法:临界区对象:线程独占,等待的线程挂起,不可调度互锁函数:单一变量同步问题事件内核对象:是否”受信”,通知信号量内核对象:信号量计数,多个线程共享,用于Socket(套接字)程序中线程同步互斥内核对象:线程独占,等待的线程可调度,用于保护内存Windows程序的执行单元共52页,您现在浏览的是第11页!3.2.1临界区对象1.为什么要线程同步?多线程同步要能保证在一个线程占有公共资源的时候,其他线程不会再占有这个资源。解决同步问题,就要保证整个存取过程的独占性。Windows程序的执行单元共52页,您现在浏览的是第12页!使用临界区对象void

InitializeCriticalSection(LPCRITICAL_SECTION

lpCriticalSection

);

ParameterslpCriticalSection

[in]Pointertothecriticalsectionobject.初始化临界区Windows程序的执行单元共52页,您现在浏览的是第13页!使用临界区对象void

LeaveCriticalSection(LPCRITICAL_SECTION

lpCriticalSection

);

ParameterslpCriticalSection

[in]Pointertothecriticalsectionobject.将临界区交还给Windows,离开临界区Windows程序的执行单元共52页,您现在浏览的是第14页!使用临界区对象使用方法说明及程序例子03CriticalSection临界区对象能够很好的保护共享数据,但是它不能够用于进程之间资源的锁定,因为它不是内核对象,如果要在进程间维持线程的同步,可以使用事件内核对象。Windows程序的执行单元共52页,您现在浏览的是第15页!互锁函数TheInterlockedIncrementfunctionincrements(increasesbyone)thevalueofthespecified32-bitvariableandcheckstheresultingvalue.Thefunctionpreventsmorethanonethreadfromusingthesamevariablesimultaneously.程序例子03InterlockDemoWindows程序的执行单元共52页,您现在浏览的是第16页!3.2.3事件内核对象事件内核对象主要用于线程间通信(同步就是一种简单的通信,指通信的数据量少)因为它是一个内核对象,所以可以跨进程使用,依靠通信,使各线程的工作协调进行,达到同步的目的。事件内核对象包括3个成员nUsageCount(使用计数)bManualReset(是否人工重置)bSignaled(是否受信)Windows程序的执行单元共52页,您现在浏览的是第17页!事件内核对象设置事件对象名称是为了在其他地方(如:其他进程的线程中)使用OpenEvent或者CreateEvent获得此内核对象句柄。HANDLEOpenEvent(DWORDdwDesiredAccess,访问权限

BOOLbInheritHandle,句柄是否被继承

LPCTSTRlpName);

事件对象名称Windows程序的执行单元共52页,您现在浏览的是第18页!事件内核对象说明当一个自动重置的事件对象受信以后,windows仅允许一个等待在该事件上的线程变为可调度状态,然后就自动重置此事件对象为未受信状态。(如果使用setevent,则上述动作是紧接着setevent进行,然后才执行setevent后面的代码)当一个人工重置事件对象受信后,所有等待在该对象上的线程都变为可调度状态。Windows程序的执行单元共52页,您现在浏览的是第19页!//放苹果

UINTWINAPIPutAppleThread(PVOIDpvParam)

{

for(inti=0;i<10;i++)

{

WaitForSingleObject(g_dish,INFINITE);

WaitForSingleObject(g_putApple,INFINITE);

cout<<"putapple!\n";

SetEvent(g_eatApple);

Sleep(5);

}

return0;

}Windows程序的执行单元共52页,您现在浏览的是第20页!//吃苹果

UINTWINAPIEatAppleThread(PVOIDpvParam)

{

for(inti=0;i<10;i++)

{

WaitForSingleObject(g_eatApple,INFINITE);

cout<<"eatapple!\n";

SetEvent(g_putApple);

SetEvent(g_dish);;

}

return0;

}Windows程序的执行单元共52页,您现在浏览的是第21页!intmain()

{

g_putApple=CreateEvent(NULL,FALSE,TRUE,NULL);

g_putBanana=CreateEvent(NULL,FALSE,TRUE,NULL);

g_eatApple=CreateEvent(NULL,FALSE,FALSE,NULL);

g_eatBanana=CreateEvent(NULL,FALSE,FALSE,NULL);

g_dish=CreateEvent(NULL,FALSE,TRUE,NULL);

HANDLEhThread[4];

intx;

hThread[0]=(HANDLE)_beginthreadex(NULL,0,PutAppleThread,(void*)&x,0,NULL);

hThread[1]=(HANDLE)_beginthreadex(NULL,0,PutBananaThread,(void*)&x,0,NULL);

hThread[2]=(HANDLE)_beginthreadex(NULL,0,EatAppleThread,(void*)&x,0,NULL);

hThread[3]=(HANDLE)_beginthreadex(NULL,0,EatBananaThread,(void*)&x,0,NULL);

charch;

cin>>ch;

return0;

}Windows程序的执行单元共52页,您现在浏览的是第22页!线程局部存储(TLS)Windows程序的执行单元共52页,您现在浏览的是第23页!线程局部存储(TLS)

系统为每一个进程都维护着一个长度为TLS_MINIMUM_AVAILABLE的位数组,TlsAlloc的返回值就是数组的一个下标(索引)。这个位数组的惟一用途就是记忆哪一个下标在使用中。初始状态下,此位数组成员的值都是FREE,表示未被使用。当调用TlsAlloc的时候,系统会挨个检查这个数组中成员的值,直到找到一个值为FREE的成员。把找到的成员的值由FREE改为INUSE后,TlsAlloc函数返回该成员的索引。如果不能找到一个值为FREE的成员,TlsAlloc函数就返回TLS_OUT_OF_INDEXES(在WinBase.h文件中定义为-1),意味着失败。Windows程序的执行单元共52页,您现在浏览的是第24页!线程局部存储(TLS)TlsSetValue和TlsGetValue分别用于设置和取得线程数组中的特定成员的值,而它们使用的索引就是TlsAlloc函数的返回值。这就充分说明了进程中惟一的位数组和各线程数组的关系。例如,TlsAlloc返回3,那就说明索引3被此进程中的每一个正在运行的和以后要被创建的线程保存起来,用以访问各自线程数组中对应的成员的值。Windows程序的执行单元共52页,您现在浏览的是第25页!线程局部存储(TLS)程序例子Windows程序的执行单元共52页,您现在浏览的是第26页!classCRapidFinder{public: CRapidFinder(intnMaxThread); virtual~CRapidFinder(); BOOLCheckFile(LPCTSTRlpszFileName); intm_nResultCount; //结果数目

intm_nThreadCount; //活动线程数目

CTypedSimpleList<CDirectoryNode*>m_listDir;//

目录列表

CRITICAL_SECTIONm_cs; //关键代码段

constintm_nMaxThread; //最大线程数目

charm_szMatchName[MAX_PATH]; //要搜索的文件

HANDLEm_hDirEvent; //向m_listDir中添加新的目 录后置位(受信)

HANDLEm_hExitEvent; //各搜索线程将要退出时置位 (受信)};Windows程序的执行单元共52页,您现在浏览的是第27页!线程的创建#defineWINAPI_stdcall;_stdcall是新标准c/c++调用方法,与标准c调用_cdecl相比,参数进栈次序相同,从右到左,但_stdcall采用自动清栈方式,而_cdecl采用手工清栈方式。由windows操作系统调用的函数(回调函数)必须声明为_stdcall没有显示说明的话,为_cdecl调用方式Windows程序的执行单元共52页,您现在浏览的是第28页!线程的创建lpsa

[in]Ignored.MustbeNULL.指定线程安全属性cbStack

[in]指定线程堆栈大小,NULL为默认大小lpStartAddr

[in]线程函数起始地址lpvThreadParam

[in]Longpointertoasingle32-bitparametervaluepassedtothethread.fdwCreate

[in]Specifiesflagsthatcontrolthecreationofthethread.0表示线程创建后立即运行lpIDThread

[out]Longpointertoa32-bitvariablethatreceivesthethreadidentifier.IfthisparameterisNULL,thethreadidentifierisnotreturned.Windows程序的执行单元共52页,您现在浏览的是第29页!WaitForSingleObjecthHandle

[in]Handletotheobject.Foralistoftheobjecttypeswhosehandlescanbespecified,seetheRemarkssection.dwMilliseconds

[in]Specifiesthetime-outinterval,inmilliseconds.Thefunctionreturnsiftheintervalelapses,eveniftheobject'sstateisnonsignaled.IfdwMillisecondsiszero,thefunctionteststheobject'sstateandreturnsimmediately.IfdwMillisecondsisINFINITE,thefunction'stime-outintervalneverelapses.Windows程序的执行单元共52页,您现在浏览的是第30页!3.1.2线程内核对象线程内核对象是一个包含了线程状态信息的数据结构。每次对CreateThread的成功调用,系统都会在内部为新的线程分配一个内核对象。系统对线程的管理是依靠访问线程内核对象来实现的。1.线程上下文CONTEXT反映该线程上次运行时CPU寄存器的状态。Windows程序的执行单元共52页,您现在浏览的是第31页!CONTEXT(上下文,即寄存器的状态),恢复现场必须使用使用计数(2):CreateThread返回句柄,相当于打开一次内核对象,促使该值+1,所以初始值=2;另外,OpenThread使该值+1,GetCurrentThread不会改变该值暂停次数(1):1表示处于暂停状态,这样就不会被调度到CPU中,给CreateThread时间对线程进行初始化。初始化之后,如果未传递CREATE_SUSPENDED(挂起)标志,暂停次数=0,该线程处于可调度状态。唤醒用ResumeThread(),该函数使暂停次数-1,挂起用SuspendThread(),该函数使暂停次数+1,一个线程可以被挂起若干次,这就需要唤醒同样次数才能使暂停次数=0,才能处于可调度状态,大约每隔20ms,进行一次时间片轮转,os选择新线程,将其上下文装入cpu寄存器,组织起运行。退出代码(STILL_ALIVE):即线程函数的返回值,可以用GetExitCodeThread得到线程退出代码,所以可以当作自定义的返回值来表示线程的执行结果。是否受信(FALSE):只有当线程结束时,该值才为TRUE…Windows程序的执行单元共52页,您现在浏览的是第32页!3.1.4线程的优先级每个线程被赋予一个优先级号,0——31,0最低,31最高,调度时选择最高的,并采用抢占式优先调度算法。eg:IEexplor的线程优先级就很高。设置优先级用SetThreadPriority()。BOOL

SetThreadPriority(HANDLE

hThread,int

nPriority

);

Windows程序的执行单元共52页,您现在浏览的是第33页!线程的优先级例子程序Windows程序的执行单元共52页,您现在浏览的是第34页!3.2线程同步同步可以保证在一个时间内只有一个线程对某个共享资源有控制权。临界区对象事件内核对象线程局部存储(TLS)Windows程序的执行单元共52页,您现在浏览的是第35页!3.2.1临界区对象1.为什么要线程同步?当多个线程在同一个进程中执行时,可能有不止一个线程同时执行同一段代码,访问同一段内存中的数据。线程同步产生的问题——一个错误的例子03ConntErr→线程函数ThreadFunc同时增加全局变量g_nCount1和g_nConnt2的计数,原则上两个变量的值应该相等,但由于是两个线程同时访问这两个全局变量,最终g_nCount1和g_nConnt2的值却不相等。Windows程序的执行单元共52页,您现在浏览的是第36页!2.使用临界区对象临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,Windows内部使用这个结构纪录一些同步信息,确保在同一时间只有一个线程访问该数据段中的数据。Windows程序的执行单元共52页,您现在浏览的是第37页!使用临界区对象void

EnterCriticalSection(LPCRITICAL_SECTION

lpCriticalSection

);

ParameterslpCriticalSection

[in]Pointertothecriticalsectionobject.申请进入临界区Windows程序的执行单元共52页,您现在浏览的是第38页!使用临界区对象void

DeleteCriticalSection(LPCRITICAL_SECTION

lpCriticalSection

);

ParameterslpCriticalSection

[in]Pointertothecriticalsectionobject.整个程序不再使用临界区的时候,将临界区删除Windows程序的执行单元共52页,您现在浏览的是第39页!3.2.2互锁函数互锁函数为同步访问多线程共享变量提供了一个简单的机制。如果变量在共享内存,不同进程的线程也可以使用此机制。互锁函数包括:InterlockedIncrementInterlockedDecrementInterlockedExchangeAddInterlockedExchangePointerWindows程序的执行单元共52页,您现在浏览的是第40页!while(g_bContinue) { ::InterlockedIncrement((long*)&g_nCount1); ::InterlockedIncrement((long*)&g_nCount2); }在主线程中等待子线程的结束,子线程的结束条件是g_bContinue=false,这就保证了在一个子线程中,g_nCount1和g_nCount2执行了相同多次。产生不一致的唯一原因是加1的操作被打断Windows程序的执行单元共52页,您现在浏览的是第41页!事件内核对象使用WaitForSingleObject来判断事件内核对象是否受信,来达到通信的目的。使用CreateEvent函数创建事件对象。HANDLE

CreateEvent(

LPSECURITY_ATTRIBUTES

lpEventAttributes,安全属性

BOOL

bManualReset,是否手动重置

BOOL

bInitialState,初始状态

LPTSTR

lpName

);

事件对象名称Windows程序的执行单元共52页,您现在浏览的是第42页!事件内核对象BOOL

SetEvent(

HANDLE

hEvent

);

将事件对象状态设置为“受信”BOOL

ResetEvent(HANDLEhEvent

);

将事件对象状态设置为“未受信”程序例子Windows程序的执行单元共52页,您现在浏览的是第43页!线程同步问题实例问题描述:**********************************

*父亲放苹果,儿子吃苹果,母亲放香蕉,女儿吃香蕉

*在一个盘子之中只能放一样水果,苹果或香蕉

**********************************创建4个线程,分别代表父亲、儿子、母亲和女儿。主线程做初始化和收尾工作Windows程序的执行单元共52页,您现在浏览的是第44页!//放香蕉

UINTWINAPIPutBananaThread(PVOIDpvParam)

{

for(inti=0;i<10;i++)

{

WaitForSingleObject(g_dish,INFINITE);

WaitForSingleObject(g_putBanana,INFINITE);

cout<<"putbanana!\n";

SetEvent(g_eatBanana);

Sleep(5);

}

return0;

}Windows程序的执行单元共52页,您现在浏览的是第45页!//吃香蕉

UINTWINAPIEatBananaThread(PVOIDpvParam)

{

for(inti=0;i<10;i++)

{

WaitForSingleObject(g_eatBanana,INFINITE);

cout<<"eatbanana!\n";

SetEvent(g_putBanana);

SetEvent(g_dish);

}

return0;

}Windows程序的执行单元共52页,您现在浏览的是第46页!3.2.6线程局部存储(TLS)线程局部存储(thread-localstorage,TLS)是一个使用很方便的存储线程局部数据的系统。利用TLS机制可以为进程中所有的线程关联若干个数据,各个线程通过由TLS分配的全局索引来访问与自己关联的数据。这样,每个线程都可以有线程局部的静态存储数据。Windows程序的执行单元共52页,您现在浏览的是第47页!线程局部存储(TLS)动态使用TLS的典型步骤如下。(1)主线程调用TlsAlloc函

温馨提示

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

评论

0/150

提交评论