windows程序设计3.ppt_第1页
windows程序设计3.ppt_第2页
windows程序设计3.ppt_第3页
windows程序设计3.ppt_第4页
windows程序设计3.ppt_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

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

文档简介

1、Windows程序设计基础,进程主线程辅助线程,第三章 windows程序的执行单元,3.1多线程,主线程在运行过程中还可以创建新的线程,即多线程。在同一进程中运行不同的线程的好处是这些线程可以共享进程的资源,如全局变量、句柄等。各个线程也可以有自己的私有堆栈用于保存私有数据。,线程的创建 线程描述了进程内代码的执行路径。进程内可以有多个线程在执行,为了使他们“同时”运行,操作系统为每个线程轮流分配CPU时间片。 一般情况下,应用程序使用主线程接受用户的输入,显示运行结果,而创建新的线程来处理长时间的操作。 每个线程必须有一个进入点函数,线程从这个进入点开始运行。,线程函数 DWORD WIN

2、API ThreadProc(LPVOID lpParam),WINAPI 是一个宏名,在windef.h文件中声明如下: #define WINAPI _stdcall _stdcall是新标准c/c+函数的调用方法。调用方法参数的进栈顺序和_cdecl一样,都是从右到左。区别: _stdcall:自动清栈 _cdecl:手动清栈 Windows规定,凡是由它负责调用的函数都必须定义为_stdcall类型。 ThreadProc是一个回调函数,即由Windows负责调用的函数。,创建新线程函数,HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThre

3、adAttribute,/线程安全性 DWORD dwStackSizem,/指定线程堆栈大小 LPTHREAD_START_ROUTINE lpStartAddress,/线程函数起始地址 LPVOID lpParameter,/传递给线程函数的参数 DWORD dwCreationFlags,/指定线程创建后是否立即启动 DWORD* lpThreadId/用于取得内核给新线程分配的ID号 );,函数执行成功,返回新建线程的线程句柄,lpStartAddress参数指定线程函数的地址,新建线程将从此地址开始执行。直到return语句返回,线程结束,把控制权交给操作系统。,需要注意的两个参数

4、:,lpThreadAttributes:一个指向SECURITY_ATTRIBUTES结构的指针,如果需要默认安全属性,传递NULL,如果希望此线程对象句柄可以被子进程继承的话,必须设定一个SECURITY_ATTRIBUTES结构。将它的bInheritHandle成员初始化为TRUE。如下 SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;/使CreateThread返回的句柄可以被继承 HANDLE h = :CreateTh

5、read(/句柄h可以被子进程继承,需要注意的两个参数:,dwCreationFlags:创建标志。如果是0,表示线程被创建后立即开始运行,如果指定为CREATE_SUSPENDED标志,表示线程被创建以后处于挂起状态,直到使用ResumeThread函数显式启动线程为止。,WaitForSingleObject(hHandle,dwMilliseconds)函数,hHandle:要等待对象的句柄 dwMilliseconds:要等待的时间 该函数用于等待指定的对象变成受信状态。 函数返回条件: 1)等待的对象变成受信状态。 2)参数dwMilliseconds指定的时间已过去。,线程内核对象

6、,线程内核对象就是一个包含了线程状态信息的数据结构。每次对CreateThread函数的成功调用,系统都会在内部为新的线程分配一个内核对象。系统提供的管理线程的函数就是依靠访问线程内核对象来实现管理的。,线程内核对象结构,创建线程内核对象时,系统对它的各个成员进行初始化。,线程上下文CONTEXT 每个线程都有自己的一组CPU寄存器,称为线程的上下文。这组寄存器的值保存在一个CONTEXT结构里,反映了该线程上次运行时CPU寄存器的状态。 使用计数Usage Count 该成员记录线程内核对象的使用次数。使用计数初始状态为2,创建一个新线程时,因为CreateThread函数返回了线程内核对象

7、的句柄,相当于打开一次,就促使Usage Count值加1。如果调用OpenThread函数,会再加1。 因此在使用完它们的句柄之后,一定要调用CloseHandle函数进行关闭。使Usage Count值减1。 如果在刚创建线程时调用CloseHandle函数关闭CreateThread函数返回的句柄,Usage Count值减为1,但线程没有被终止。线程结束要关闭句柄,不然内存泄露。,暂停次数Suspend Count 该成员用于指明线程的暂停计数。 DWORD ResumeThread(HANDLE hThread);/唤醒挂起线程 DWORD SuspendThread(HANDLE

8、hThread);/挂起线程 上面两个函数用于减少或增加Suspend Count。 在相同优先级下,当Suspend Count变为0时,系统可以调度该线程. 退出代码Exit Code Exit Code指定线程的退出代码,也可以说是线程函数的返回值。在线程运行期间,线程函数没有返回,值为STILL_ACTIVE。当线程结束后,系统自动将ExitCode设为线程函数的返回值。可以用GetExitCodeThread函数得到线程的退出代码。,是否受信 Signaled Signaled指示了线程对象是否为受信状态。运行期间FALSE,即未受信,线程结束后,系统把其置为TRUE,针对此对象的等

9、待函数(如WaitForSingleObject)就会返回。,线程的终止,线程正常终止时,会发生以下事件: 在线程函数中创建的所有C+对象将通过它们各自的析构函数被正确的销毁。 该线程使用的堆栈被释放。 系统将线程内核对象中ExitCode的值由STILL_ACTIVE设置为线程函数返回的值。 系统将递减线程内核对象中Usage Code的值。,线程的终止有四种方法,1.线程函数自然退出。 2.使用ExitThread函数来终止线程。 3.使用TerminateThread函数在一个线程中强制终止另一个线程的执行。 4.使用ExitProcess函数结束进程。,线程的优先级,线程的优先级范围从

10、0(最低)到31(最高)。当你产生线程时,并不是直接以数值指定其优先级,而是采用两个步骤。第一个步骤是指定“优先级等级(Priority Class)”给进程,第二步骤是指定“相对优先级”给该进程所拥有的线程。其中的代码在CreateProcess的dwCreationFlags参数中指定。如果你不指定,系统默认给的是NORMAL_PRIORITY_CLASS,除非父进程是IDLE_PRIORITY_CLASS(那么子进程也会是IDLE_PRIORITY_CLASS)。 要改变线程优先级,用如下函数: BOOL SetThreadPriority(HANDLE hThread,int nPri

11、ority),线程的优先级值,SetThreadPriority 的 参 数: THREAD_PRIORITY_IDLE 空闲 THREAD_PRIORITY_LOWEST 最低 THREAD_PRIORITY_BELOW_NORMAL 低于正常(98) THREAD_PRIORITY_NORMAL 正常 THREAD_PRIORITY_ABOVE_NORMAL 高于正常(98) THREAD_PRIORITY_HIGHEST 最高 THREAD_PRIORITY_TIME_CRITICAL 实时,C/C+运行期库,在实际的开发过程中,一般不直接使用系统提供的CreateThread函数创建线

12、程,而是使用c/c+运行期函数_beginthreadex。 unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned (_stdcall *start_address)(void*), void *arglist, unsigned initflag, unsigned *thrdaddr );,C/C+运行期库,事实上,这个函数是为了多线程同步的需要。 相应地,系统使用c/c+运行期函数_endthreadex代替ExitThread函数。 void _endthreadex(unsigned r

13、etval); 这个函数释放_beginthreadex为保持线程同步而申请的内存空间,然后再调用ExitThread函数来终止线程。,同步可以保证在一个时间内只有一个线程对某个共享资源有控制权。共享资源包括全局变量、公共数据成员或者句柄等。临界区对象和事件内核对象可以很好的用于多线程同步和它们之间的通信。,3.2 线程同步,1 临界区对象 临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,Windows内部使用这个结构记录一些信息,确保在同一时间只有一个线程访问该数据段的数据。,编程时,要把临界区对象定义在想保护的数据段中,然后在任何线程使用此临界区对象之前对它进行初始化

14、。 void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) /指向数据段中定义的CRITICAL_SECTION结构 之后,线程访问临界区中数据的时候,必须调用EnterCriticalSection函数,申请进入临界区(关键代码段)。若一个线程在临界区,函数就会一直等下去。 void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection),当操作完成时,还要将临界区还给Windows,以便其他线程可以申请使用。 void LeaveCriticalS

15、ection(LPCRITICAL_SECTION lpCriticalSection) 当程序不再使用临界区对象的时候,必须使用DeleteCriticalSection函数将它删除。 void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) 临界区对象能够很好的保护共享数据,但是它不能够用于进程之间资源的锁定,因为它不是内核对象。如果要在进程间维持线程的同步可以使用事件内核对象。,2 互锁函数,互锁函数为同步访问多线程共享变量提供了一个简单的机制。如果变量在共享内存,不同进程的线程也可以使用此机制。 用于互锁的函数有I

16、nterlockedIncrement、InterlockedDecrement、InterlockedExchangeAdd、InterlockExchangePointer等。 如果只是简单的保护一个共享变量,可以使用互锁函数:LONG InterlockedIncrement(LONG volatile* Addend); /递增指定的32位变量LONG InterlockedDecrement(LONG volatile* Addend); /递减指定的32位变量这两个函数可以阻止其他线程同时访问此变量。,其他互锁函数,(1) LONG InterlockedExchangeAdd (

17、 LPLONG Addend, LONG Increment );Addend为长整型变量的地址,Increment为想要在Addend指向的长整型变量上增加的数值(可以是负数)。这个函数的主要作用是保证这个加操作为一个原子访问。(2) LONG InterlockedExchange( LPLONG Target, LONG Value );用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。

18、(4) LONG InterlockedCompareExchange( LPLONG Destination, LONG Exchange, LONG Comperand );如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。(5) PVOID InterlockedCompareExchangePointer (PVOID *Destination, PVOID Exchange, PVOID Comperand );如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。,3 事件内核对象 多线

19、程程序设计大多会涉及线程间相互通信。主线程在创建工作线程时,可以通过参数给工作线程传递初始化数据,当工作线程开始运行后,还需要通过通信机制来控制工作线程。同样,工作线程有时也需要将一些情况主动通知主线程。一种比较好的方法就是使用事件内核对象。,事件内核对象 事件内核对象是一种抽象的对象,它也有未受信和受信两种状态,可以使用WaitForSingleObject函数等待其变成受信状态。不同于其他内核对象的是一些函数可以使事件对象在这两种状态之间转化。可以把事件对象看成是一个设置在Windows内部的一个标志。他的状态设置和测试工作由Windows来完成。 事件对象包含3个成员: nUsageCo

20、unt :使用计数 bManualReset :是否人工重置 bSignaled :是否受信,基本函数 使用CreateEvent函数创建事件对象: HANDLE CreateEvent( LPSECURITY_ATTTIBUTES lpEventAttributes, / 用来定义事件对象的安全属性 BOOL bManualReset, / 指定是否需要手工重置事件对象为为受信状态 BOOL bInitialState, / 指定事件对象创建时的初始状态 LPCWSTR lpName); / 事件对象的名称 根据lpName对象的名字查询,返回事件对象句柄: HANDLE OpenEvent

21、( DWORD dwDesiredAccess, / 指定想要的访问权限 BOOL bInheritHandle, / 指定返回句柄是否被继承 LPCWSTR lpName); / 要打开的事件对象的名称 BOOL SetEvent(HANDLE hEvent); / 将事件状态设为“受信(sigaled)” BOOL ResetEvent(HANDLE hEvent); / 将事件状态设为“未受信(nonsigaled)”,4 信号量内核对象 信号量内核对象允许多个线程在同一时刻访问统一资源,但需要限制在同一时刻访问此资源的最大线程数据。 -创建信号量对象 HANDLE CreateSema

22、phore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,/安全属性指针 LONG lInitialCount,/初始计数 LONG lMaximumCount,/最大计数 LPCTSTR lpName /对象名指针 );,打开信号量内核对象 HANDLE OpenSemaphore( DWORD dwDesireAccess,/访问标志 BOOL bInheritHandle,/继承标志 LPCTSTR lpName /对象名指针 ); 内核对象,可以在其他进程中通过名字得到并打开,释放信号量内核对象 BOOL ReleaseSemaphore(

23、HANDLE hSemaphore,/信号量句柄 LONG lReleaseCount,/级数递增数量 LPLONG lpPreviousCount /先前计数 );,5 互斥内核对象 互斥内核对象,可以保证多个线程对同一共享资源的互斥访问,与临界区类似。只有拥有互斥对象的线程才具有访问资源的权限。由于互斥对象只有一个,因此决定了在任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。 与其他内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。,创建或打开互斥内核对象 HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,/安全属性指针 BOO

温馨提示

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

评论

0/150

提交评论