




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
中科研CASoft软件工程师培训讲义Game
Master游戏修改工具第13讲Windows线程基础中科天地软件人才培训中心Created
by本讲重点提要¤
本讲对Windows线程概念进行了较为详细的介绍,并通过MultiThread范例练习了多线程技术的使用。多任务和多线程¤
多任务是一个操作系统可以同时运行多个程序的能力。操作系统使用一个硬件时钟为同时运行的每个进程分配“时间片”,虽然操作系统在任一时间点只能执行一个任务,但如果时间片足够小,在宏观上,用户感觉到多个任务是同时执行的。¤
多线程是在一个程序内部实现多任务的能力。
程序可以把它自己分割为单独的执行线程,就
像多任务一样,这些线程也是“同时执行的”。¤
Windows的16位版本提供了对多任务的支持,
32位版本开始支持多线程。多线程体系结构¤
对于Windows的图形环境,比较合理的多线程体系结构为:主线程创建程序所需的所有窗口,并在其中包括所有的窗口过程,以便处理这些窗口的所有消息;其它的所有线程只进行一些后台处理,除了和主线程通讯外,它们不和用户进行任何交流。Windows线程¤
为什么要使用线程?¤
线程的栈¤
创建一个线程:CreateThread函数¤
终止线程¤
线程调度¤
实验:Process
Viewer工具Windows线程概述¤
线程由两个部分组成:–一个是线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方–一个是线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。这意味着线程在它的进程地址空间中执行代码,并且在进程的地址空间中对数据进行操作。Windows线程概述¤因此,如果在单进程环境中,有两个或多个线程正在运行,那么这两个线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。¤进程使用的系统资源比线程多得多,原因是它需要更多的地址空间。为进程创建一个虚拟地址空间需要许多系统资源。系统中要保留大量的记录,这要占用大量的内存。另外,由于exe和dll文件要加载到一个地址空间,因此也需要文件资源。而线程使用的系统资源要少得多。实际上,线程只有一个内核对象和一个堆栈,保留的记录很少,因此需要很少的内存。由于线程需要的开销比进程少,因此始终都应该设法用增加线程来解决编程问题,而要避免创建新的进程。Windows线程概述¤
线程用于描述进程中的运行路径,每当进程被初始化时,系统就要创建一个主线程。该线程与C/C++运行期库的启动代码一道开始运行,启动代码则调用进入点函数(main、wmain、WinMain或wWinMain),并且继续运行直到进入点函数返回并且C/C++运行期库的启动代码调用ExitProcess为止。对于许多应用程序来说,这个主线程是应用程序需要的唯一线程。不过,进程能够创建更多的线程来帮助执行它们的操作。Windows线程概述¤
设计一个拥有多线程的应用程序,就会扩大该应用程序的功能。¤
虽然多线程应用程序的优点很多,但是它也存在某些不足之处。例如数据共享问题。因此在引入多线程时,也会引入一些其他问题。¤
因此,应该慎重地使用多线程技术,在单线程能够处理的很好的情况下,就不必要去使用多线程。Windows线程:入口函数¤
所有的线程都开始执行于某个必须指定的函数,该函数原型必须如下:DWORD
WINAPI
ThreadFunction(LPVOID
lpParam);¤
像WinMain一样,实际上该函数不是被操作系统调用的,操作系统调用了一个
C运行时的内部函数,我们可以称之为
StartOfThread,实际的内部名称是什么并不重要,下面的代码大致显示了该函数的样子:Windows线程:入口函数VOID
StartOfThread(LPTHREAD_START_ROUTINE
lpStartAddr,LPVOID
lpvThreadParam)
{
try{DWORD
dwThreadExitCode
=
lpStartAddr(lpvThreadParam);ExitThread(dwThreadExitCode);}
except(UnhandledExceptionFilter(GetExceptionInformation)){ExitProcess(GetExceptionCode());}}Windows线程:入口函数¤
StartOfThread函数进行以下操作:建立一个围绕着线程函数的结构化异常处理(SEH)框架,这样线程运行时产生的异常就由系统进行缺
省的处理。调用编写的线程函数,把传递给CreateThread的32位参数lpvThreadParam传递给它。当线程函数返回时,StartOfThread函数调用
ExitThread,传给它线程函数的返回值。线程内核对象的引用计数减少,线程停止运行。如果线程产生了没有被处理的异常,由
StartOfThread函数的SHE框架会处理该异常,通常,它提供给用户一个对话框,对话框被取消时,调用ExitProcess终结整个进程,而不仅仅是线程!Windows线程:入口函数¤
上一讲介绍进程的时候我们没有提到
StartOfThread函数,但实际上进程的主线程是从执行系统的StartOfThread函数开始的,该函数然后调用C运行时的启动代码,后者载调用WinMain函数。不过,
C运行时的启动代码并不返回到
StartOfThread函数,因为启动代码显式调用了ExitProcess函数。Windows线程:相关属性¤
下面将讨论线程所具有的一些属性。线程的栈线程的CONTEXT结构线程的执行时间Windows线程:相关属性1.
线程的栈每个线程都从所在进程的4GB地址空间中分配了自
己的栈(STACK)。当使用静态变量或全局变量时,多个线程有可能同时访问这些变量,必须使用同步
机制来保护这些变量,否则变量内容可能会被破坏。然而,局部变量和自动变量(未加static说明的局部
变量即自动变量)是创建在线程的栈上的,因此不
太可能被另一线程破坏,所以,在编写函数时,要
尽量使用局部或自动变量。当然线程栈的资源非常
宝贵,使用局部变量也必须小心谨慎。有两点必须
注意:过大的局部变量数组和过深的函数递归调用
有可能导致栈溢出(STATCK_OVERFLOW),这时有可能导致整个进程终结。Windows线程:相关属性2.
线程的CONTEXT结构每个线程有自己的一组CPU寄存器结构,叫做上下文(Context)。CONTEXT结构反应了线程上次执行时CPU寄存器的状态。该结构是Win32数据结构中唯一与CPU相关的结构,实际上Win32的帮助文档没有给出该结构的说明,可以查看WINNT.H来了解该结构的成员。当线程被调度到使用CPU时,系统用它的上下文来初始化CPU的寄存器,当然,其中的一个寄存器是指令指针,指出了线程要执行的下一条CPU指令的地址。还有一个栈指针寄存器,指明线程栈的地址。查看MSDN文档GetThreadContext函数的说明,可以了解更多的信息。Windows线程:相关属性3.线程的执行时间在多线程环境下,很难测定进程用了多少时间来完成某项任务。这是因为线程的执行经常会被抢先,很多人喜欢用下面的代码来测定算法的时间:DWORD
dwStartTime
=
GetTickCount();//执行算法…DWORD
dwElapsedTime
=
GetTickCount()
–
dwStartTime;事实上,这样的办法是很不准确的。Win32提供了另一个函数来帮助计算线程的执行时间:BOOL
GetThreadTimes(
HANDLE
hThread,//创建线程的时间//线程退出的时间//线程在内核运行的时间//线程在用户模式运行的时间LPFILETIME
lpCreationTime,LPFILETIME
lpExitTime,LPFILETIME
lpKernelTime,LPFILETIME
lpUserTimie);Windows线程:相关属性¤
在一个算法执行的前后使用该函数,将获取的时间相减就可以得到线程运行的准确时间,该时间为线程运行于内核态和用户态时间的总和。¤
Win32还提供了一个GetProcessTimes函数用于获取某个进程中所有线程的执行时间和。Windows线程:CreateThread3.
创建一个线程:CreateThread函数。函数原型如下图所示:Windows线程:CreateThread¤
每次调用CreateThread函数,系统执行下列步骤:分配一个线程内核对象来标识和管理新创建的线程,该对象的句柄由CreateThread函数返回。把线程的退出码(包含在线程内核对象中)初始化为STILL_ACTIVE,把线程的挂起计数设置为1。为新线程分配一个CONTEXT结构。通过保留一块地址空间来准备线程的栈。lpStartAddress和lpParameter值被放在栈的顶部,使它们成为传递给StartOfThread的参数。把线程的CONTEXT结构中的栈指针寄存器指向栈顶,把指令寄存器指向内部的StartOfThread函数。Windows线程:CreateThread¤
CreateThread函数的入口参数如下:–lpThreadAttributes
线程的安全属性,指向一个
SECURITY_ATTRIBUTES结构的指针,设置为NULL则使用缺省的安全属性。如果想让子进程能够继承该线程对象的句柄,必须设定该结构,并将该结构的成员bInheritHandle初始化为TRUE。–dwStackSize
指定线程使用的栈的地址空间大小,置为0则使用缺省设置:保留1M空间,并提交1页内存。随着当调用栈的增加,系统会在1M空间内提交更多的页,如果超过1M,则产生栈溢出异常。–lpStartAddress
指明想要新线程执行的线程函数的地址。该函数由用户定义,但原型必须为之前我们介绍过的线程入口函数原型。Windows线程:CreateThread–lpParameter
传递给线程入口函数的参数。该参数可以是一个32位的值,一般是一个包含附加信息的数据结构的32位指针。–dwCreationFlags
设定用于控制创建线程的标志,该标志可以取0值或CREATE_SUSPENDED,取0值的情况,线程将在创建后立即执行,如果指定
CREATE_SUSPENDED标志,则线程创建后挂起,直到
ResumeThread函数调用才开始执行。–lpThreadId
这个地址来返回系统分配给新线程的
ID,它必须是一个有效的DWORD值的地址。在
Windows
9x平台上,该参数不能为空,WindowsNT4.0/2K平台上,可以置为NULL而忽略线程ID。Windows线程:终止线程终止线程终止线程的运行,可以使用下面的方法:线程函数返回(最好使用这种方法)。通过调用ExitThread函数,线程将自行撤消(最好不要使用这种方法)。同一个进程或另一个进程中的线程调用
TerminateThread函数(应该避免使用这种方法)。包含线程的进程终止运行(应该避免使用这种方法)。Windows线程:终止线程¤
线程函数返回始终都应该将线程设计成这样的形式,即
当想要线程终止运行时,它们就能够返回。这
是确保所有线程资源被正确地清除的唯一办法。如果线程能够返回,就可以确保下列事项的实现:在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。操作系统将正确地释放线程堆栈使用的内存。系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。系统将递减线程内核对象的使用计数。Windows线程:终止线程¤
ExitThread函数可以让线程调用ExitThread函数,以便强制线程终止运行。该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。由于这个原因,最好从线程函数返回,而不是通过调用ExitThread来返回。Windows线程:终止线程¤
TerminateThread函数调用TerminateThread函数也能够终止线程的运行。与ExitThread不同,ExitThread总是撤消调用的线程,而TerminateThread能够撤消任何线程。hThread参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。注意TerminateThread函数是异步运行的函数,也就是说,它告诉系统要线程终止运行,但是,当函数返回时,不能保证线程被撤消。Windows线程:终止线程¤
线程终止时发生的操作当线程终止运行时,会发生下列操作:线程拥有的所有用户对象均被释放。线程终止运行时,系统会自动撤消任何窗口,并且卸载线程创建的或安装的任何挂钩。其他对象只有在拥有线程的进程终止运行时才被撤消。线程的退出代码从STILL_ACTIVE改为传递给
ExitThread或TerminateThread的代码。线程内核对象的状态变为已通知。如果线程是进程中最后一个活动线程,系统也将进程视为已经终止运行。线程内核对象的使用计数递减1。Windows线程:终止线程¤
当一个线程终止运行时,在与它相关联的线程
内核对象的所有未结束的引用关闭之前,该内
核对象不会自动被释放。一旦线程不再运行,
系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用GetExitcodeThread来
检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码,尚未终止运行,该函数就用STILL_ACTIVE标识符填入DWORD。如果该函数运行成功,便返回TRUE。Windows线程:挂起和恢复¤
挂起和恢复线程前面提到过通过创建标志线程可以创建在挂起状态(进程也可以)。为了使线程执行,必须调用ResumeThread函数,同样,也可以使一个正在运行的线程挂起:调用
SuspendThread函数。这两个函数都接受线程句柄作为唯一的参数。对于创建时挂起的一个进程,要恢复其运行需要在ResumeThread函数的入口参数中传入其主线程的句柄,如:ResumeThread(process_info.
hThread
);范例:多线程应用¤
练习:编写一个基于对话框的MFC应用程序,外观如下:其按钮功能设计如下:点击Create…按钮,根据是否
选中Suspend选择框创建一个挂起或运行的线程(该线程可以选择一种可以表明自己在运行的方式显示一些
信息),点击按钮Run、Pause、Stop使线程运行、暂停或终止。通过Enable、Disable按钮防止用户误操作。范例:多线程应用¤
在附带的范例代码中,我们使用了向一个Console输出字符的方式显示线程运行信息,关于使用Console的情况,参见Windows进程基础一讲。下面的图显示了范例运行时的情况:Windows线程:ID和句柄¤
一些Win32函数需要一个进程句柄作为它们的参数。线程可以调用函数GetCurrentProcess来得到它所在的进程的句柄。该函数返回一个指向进程的伪句柄。说是伪句柄,是因为它不创建新句柄,也不增加对进程对象的使用计数。如果把该句柄作为参数调用CloseHandle,
CloseHandle只是简单地忽略这次调用,不作任何操作就返回了。¤
Win32中还包含了一些需要使用进程ID的函数,线程可以调用GetProcessId来得到当前进程的
ID,该函数返回标识该进程唯一的系统ID值。Windows线程:ID和句柄¤
类似的,函数GetCurrentThread用于获取当前线程的伪句柄,函数
GetCurrentThreadId用于获取当前线程的ID值。¤
有时需要获取线程或进程的真正句柄,而不是一个伪句柄。所谓真正,是指唯一的线程或进程句柄。将伪句柄转换为真正的句柄,需要调用函数
DuplicateHandle。关于
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论