MFC多线程程序设计Word文档格式.docx
- 文档编号:18410626
- 上传时间:2022-12-16
- 格式:DOCX
- 页数:17
- 大小:25.65KB
MFC多线程程序设计Word文档格式.docx
《MFC多线程程序设计Word文档格式.docx》由会员分享,可在线阅读,更多相关《MFC多线程程序设计Word文档格式.docx(17页珍藏版)》请在冰豆网上搜索。
进程(process)是什么?
给你一分钟时间。
zzzzz...
你的回答可能是:
『一个可执行档执行起来,就是一个进程』。
唔,也不能算错。
但能
不能够有更具体的答案?
再问你一个问题:
模块(module)是什么?
可能你的回答还是:
『一个可执行档执行起来,就是一个模块』。
这也不能够算错。
但是你明明知道,模块
不等于进程。
KERNEL32DLL是一个模块,但不是一个进程;
ScribbleEXE是一个模块,
也是一个进程。
我们需要更具体的资料,更精准的答案。
如果我们能够知道操作系统如何看待模块和进程,就能够给出具体的答案了。
一段可执
行的程序(包括EXE和DLL),其程序代码、资料、资源被加载到内存中,由系统建
置一个数据结构来管理它,就是一个模块。
这里所说的数据结构,名为ModuleDatabase
(MDB),其实就是PE格式中的PE表头,你可以从WINNT.H档中找到一个
IMAGE_NT_HEADER结构,就是它。
好,解释了模块,那么进程是什么?
这就比较抽象一点了。
这样说,进程就是一大堆拥
有权(ownership)的集合。
进程拥有地址空间(由memorycontext决定)、动态配置而
来的内存、文件、执行线程、一系列的模块。
操作系统使用一个所谓的ProcessDatabase
(PDB)数据结构,来记录(管理)它所拥有的一切。
执行线程呢?
执行线程是什么?
进程主要表达「拥有权」的观念,执行线程则主要表达模块中
的程序代码的「执行事实」。
系统也是以一个特定的数据结构(ThreadDatabase,TDB)记
录执行线程的所有相关资料,包括执行线程区域储存空间(ThreadLocalStorage,TLS)、讯
息队列、handle表格、地址空间(MemoryContext)等等等。
最初,进程是以一个执行线程(称为主执行线程,primarythread)做为开始。
如果需要,行
程可以产生更多的执行线程(利用CreateThread),让CPU在同一时间执行不同段落的
码。
当然,我们都知道,在只有一颗CPU的情况下,不可能真正有多任务的情况发生,
「多个执行线程同时工作」的幻觉主要是靠排程器来完成--它以一个硬件定时器和一组复
杂的游戏规则,在不同的执行线程之间做快速切换动作。
以Windows95和WindowsNT
而言,在非特殊的情况下,每个执行线程被CPU照顾的时间(所谓的timeslice)是20个
milliseconds。
如果你有一部多CPU计算机,又使用一套支持多CPU的操作系统(如WindowsNT),
那么一个CPU就可以分配到一个执行线程,真正做到实实在在的多任务。
这种操作系统特性
称为symmetricmultiprocessing(SMP)。
Windows95没有SMP性质,所以即使在多CPU
计算机上跑,也无法发挥其应有的高效能。
图14-1表现出一个进程(PDB)如何透过「MODREF串行」连接到其所使用的所有模
组。
图14-2表现出一个模块数据结构(MDB)的细部内容,最后的DataDirectory[16]记
录着16个特定节区(sections)的地址,这些sections包括程序代码、资料、资源。
图
14-3表现出一个执行线程数据结构(PDB)的细部内容。
当Windows加载器将程序加载内存中,KERNEL32挖出一些内存,构造出一个
PDB、一个TDB、一个以上的MDBs(视此程序使用到多少DLL而定)。
针对TDB,
操作系统又要产生出memorycontext(就是在操作系统书籍中提到的那些所谓page
tables)、消息队列、handle表格、环境数据结构(EDB)...。
当这些系统内部数据结构
都构造完毕,指令指位器(InstructionPointer)移到程序的进入点,才开始程序的执行。
图14-1进程(PDB)透过「MODREF串行」连接到其所使用的所有模块
执行线程优先权(Priority)
我想我们现在已经能够用很具体的形象去看所谓的进程、模块、执行线程了。
「执行事实」
发生在执行线程身上,而不在进程身上。
也就是说,CPU排程单位是执行线程而非进程。
排
程器据以排序的,是每个执行线程的优先权。
优先权的设定分为两个阶段。
我已经在第1章介绍过。
执行线程的「父亲大人」(进程)
拥有所谓的优先权等级(priorityclass,图1-7),可以在CreateProcess的参数中设定。
执行线程基本上继承自其「父亲大人」的优先权等级,然后再加上CreateThread参数中的
微调差额(-2~+2)。
获得的结果(图1-8)便是执行线程的所谓basepriority,范围从0~31
数值愈高优先权愈高。
:
SetThreadPriority是调整优先权的工具,它所指定的也是微调差
额(-2~+2)。
//指向各个sections,
//包括程序代码,资料,资源
图14-2模块数据结构MDB的细部内容(资料整理自Windows95System
ProgrammingSECRETS,MattPietrek,IDGBooks)
执行线程排程(Scheduling)
排程器挑选「下一个获得CPU时间的执行线程」的唯一依据就是:
执行线程优先权。
如果
所有等待被执行的执行线程中,有一个是优先权16,其它所有执行线程都是优先权15(或
更低),那么优先权16者便是下一个夺标者。
如果执行线程A和B同为优先权16,排程
器会挑选等待比较久的那个(假设为执行线程A)。
当A的时间切片(timeslice)终了,如
果B以外的其它执行线程的优先权仍维持在15(以下),执行线程B就会获得执行权。
「如果B以外的其它执行线程的优先权仍维持在15(以下)...」,唔,这听起来彷佛优先
权会变动似的。
的确是。
为了避免朱门酒肉臭、路有冻死骨的不公平情况发生,排程器
会弹性调整执行线程优先权,以强化系统的反应能力,并且避免任何一个执行线程一直未能
接受CPU的润泽。
一般的执行线程优先权是7,如果它被切换到前景,排程系统可能暂
时地把它调升到8或9或更高。
对于那些有着输入消息等待被处理的执行线程,排程系
统也会暂时调高其优先权。
对于那些优先权本来就高的执行线程,也并不是有永久的保障权利。
别忘了Windows毕竟
是个消息驱动系统,如果某个执行线程调用:
GetMessage而其消息队列却是空的,这个执
行线程便被冻结,直到再有消息进来为止。
冻结的意思就是不管你的优先权有多高,暂时
退出排班行列。
执行线程也可能被以:
SuspendThread强制冻结住(:
ResumeThread可以解
除冻结)。
会被冻结,表示这个执行线程「要去抓取消息,而执行线程所附带的消息队列中却没有消息」。
如果一个执行线程完全和UI无关呢?
是否它就没有消息队列?
倒不是,但它的程序代码中
没有消息循环倒是事实。
是的,这种执行线程称为workerthread。
正因它不可能会被冻结,
所以它绝对不受Win16Mutex或其它因素而影响其强制性多任务性质,及其优先权。
ThreadContext
Context一词,我不知道有没有什么好译名,姑且就用原文吧。
它的直接意思是「前后关
系、脉络;
环境、背景」。
所以我们可以说ThreadContext是构成执行线程的「背景」。
那是指什么呢?
狭义来讲是指一组缓存器值(包括指令指位器IP)。
因为执行线程常常会
被暂停,被要求把CPU拥有权让出来,所以它必须将暂停之前一刻的状态统统记录下
来,以备将来还可以恢复。
你可以在WINNT.H中找到一个CONTEXT数据结构,它可以用来储存Thread
Context。
GetThreadContext和:
SetThreadContext可以取得和设定某个执行线程的
context,因而改变该执行线程的状态。
这已经是非常低阶的行为了。
MattPietrek在其
Windows95SystemProgrammingSECRETS一书第10章,写了一个Win32APISpy程
式,就充份运用了这两个函数。
我想我们在操作系统层面上的执行线程学理基础已经足够了,现在让我们看看比较实际一
点的东西。
从程序设计层面看执行线程
如果要从程序设计层面来了解执行线程,JimBeveridge和RobertWiener合着
的MultithreadingApplicationsinWin32(Win32多线程程序设计/侯俊杰译/�峰出版)是
很值得推荐的一份知识来源。
这本书介绍执行线程的学理观念、程序方法、同步控制、资
料一致性的保持、Cruntimelibrary的多线程版本、C++的多线程程序方法、MFC中的多线程
程序方法、除错、进程通讯(IPC)、DLLs...,以及约50页的实际应用。
JeffreyRichter的AdvancedWindows在进程与执行线程的介绍上(第2章和第
3章),也有非常好的表现。
他的切入方式是详细而深入地叙述相关Win32API的规格
与用法。
并举实例左证。
如何产生执行线程?
我想各位都知道了,:
CreateThread可以办到。
图14-4是与执行线程有
关的Win32API。
与执行线程有关的Win32API功能
AttachThreadInput将某个执行线程的输入导向另一个执行线程
CreateThread产生一个执行线程
ExitThread结束一个执行线程
GetCurrentThread取得目前执行线程的handle
GetCurrentThreadId取得目前执行线程的ID
GetExitCodeThread取得某一执行线程的结束代码(可用以决定执行线程是否
已结束)
GetPriorityClass取得某一进程的优先权等级
GetQueueStatus传回某一执行线程的消息队列状态
GetThreadContext取得某一执行线程的context
GetThreadDesktop取得某一执行线程的desktop对象
GetThreadPriority取得某一执行线程的优先权
GetThreadSelectorEntry除错器专用,传回指定之执行线程的某个selector的
LDT记录项
ResumeThread将某个冻结的执行线程恢复执行
SetPriorityClass设定优先权等级
SetThreadPriority设定执行线程的优先权
Sleep将某个执行线程暂时冻结。
其它执行线程将获得执行权。
SuspendThread冻结某个执行线程
TerminateThread结束某个执行线程
TlsAlloc配置一个TLS(ThreadLocalStorage)
TlsFree释放一个TLS(ThreadLocalStorage)
TlsGetValue取得某个TLS(ThreadLocalStorage)的内容
TlsSetValue设定某个TLS(ThreadLocalStorage)的内容
WaitForInputIdle等待,直到不再有输入消息进入某个执行线程中
图14-4与执行线程有关的Win32API函数
注意,多执行线程并不能让程序执行得比较快(除非是在多CPU机器上,并且使用支持
symmetricmultiprocessing的操作系统),只是能够让程序比较「有反应」。
试想某个程
式在某个菜单项目被按下后要做一个小时的运算工作,如果这份工作在主执行线程中做,
而且没有利用PeekMessage的技巧时时观看消息队列的内容并处理之,那么这一个小时
内这个程序的使用者接口可以说是被冻结住了,将毫无反应。
但如果沉重的运算工作是
由另一个执行线程来负责,使用者接口将依然灵活,不受影响。
WorkerThreads和UIThreads
从Windows操作系统的角度来看,执行线程就是执行线程,并未再有什么分类。
但从MFC
的角度看,则把执行线程划分为和使用者接口无关的workerthreads,以及和使用者接口
(UI)有关的UIthreads。
基本上,当我们以:
CreateThread产生一个执行线程,并指定一个执行线程函数,它就是一
个workerthread,除非在它的生命中接触到了输入消息--这时候它应该有一个消息回
路,以抓取消息,于是该执行线程摇身一变而为UIthread。
注意,执行线程本来就带有消息队列,请看图14-3的TDB结构。
而如果执行线程程序代码
中带有一个消息循环,就称为UIthread。
错误观念
我记得曾经在微软的技术文件中,也曾经在微软的范例程序中,看到他们鼓励这样的作
法:
为程序中的每一个窗口产生一个执行线程,负责窗口行为。
这种错误的示范尤其存在
于MDI程序中。
是的,早期我也沾沾自喜地为MDI程序的每一个子窗口设计一个执
行线程。
基本上这是错误的行为,要付出昂贵的代价。
因为子窗口一切换,上述作法会导
至执行线程也切换,而这却要花费大量的系统资源。
比较好的作法是把所有UI(User
Interface)动作都集中在主执行线程中,其它的「纯种运算工作」才考虑交给workerthreads
去做。
正确态度
什么是使用多执行线程的好时机呢?
如果你的程序有许多事要忙,但是你还要随时保持注
意某些外部事件(可能来自硬件或来自使用者),这时就适合使用多执行线程来帮忙。
以通讯程序为例。
你可以让主执行线程负责使用者接口,并保持中枢的地位。
而以一个分
离的执行线程处理通讯端口,
我已经在第1章以一个小节介绍了Win32多线程程序的写法,并给了一个小范例
MltiThrd。
这一节,我要介绍MFC多线程程序的写法。
探索CWinThread
就像CWinApp对象代表一个程序本身一样,CWinThread对象代表一个执行线程本身。
这
个MFC类别我们曾经看过,第6章讲「MFC程序的生死因果」时,讲到「CWinApp:
Run
-程序生命的活水源头」,曾经追踪过CWinApp:
Run的源头CWinThread:
Run(里面有
一个消息循环)。
可见程序的「执行事实」系发生在CWinThread对象身上,而CWinThread
对象必须要(必然会)产生一个执行线程。
我希望「CWinThread对象必须要(必然会)产生一个执行线程」这句话不会引起你的误会,
以为程序在applicationobject(CWinApp对象)的构造式必然有个动作最终调用到
CreateThread或_beginthreadex。
不,不是这样。
想想看,当你的Win32程序执行起来,
你的程序并没有调用CreateProcess为自己做出代表自己的那个进程,也没有调用
CreateThread为自己做出代表自己的主执行线程(primarythread)的那个执行线程。
为你的
程序产生第一个进程和执行线程,是系统加载器以及核心模块(KERNEL32)合作的结果。
所以,再次循着第6章一步步剖析的步骤,MFC程序的第一个动作是CWinApp:
CWinApp
(比WinMain还早),在那里没有「产生执行线程」的动作,而是已经开始在收集执行线程
的相关信息了:
//inMFC4.2APPCORE.CPP
CWinApp:
CWinApp(LPCTSTRlpszAppName)
{
...
//initializeCWinThreadstate
AFX_MODULE_STATE*pModuleState=_AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE*pThreadState=pModuleState->
m_thread;
ASSERT(AfxGetThread()==NULL);
pThreadState->
m_pCurrentWinThread=this;
ASSERT(AfxGetThread()==this);
m_hThread=:
GetCurrentThread();
m_nThreadID=:
GetCurrentThreadId();
}
虽然MFC程序只会有一个CWinApp对象,而CWinApp衍生自CWinThread,但并不
是说一个MFC程序只能有一个CWinThread对象。
每当你需要一个额外的执行线程,不
应该在MFC程序中直接调用:
CreateThread或_beginthreadex,应该先产生一个
CWinThread对象,再调用其成员函数CreateThread或全域函数AfxBeginThread将执行
线程产生出来。
当然,现在你必然已经可以推测到,CWinThread:
CreateThread或
AfxBeginThread内部调用了:
CreateThread或_beginthreadex(事实上答案是
_beginthreadex)。
这看起来颇有值得商议之处:
为什么CWinThread构造式不帮我们调用AfxBeginThread
呢?
似乎CWinThread为德不卒。
图14-5就是CWinThread的相关源代码。
#0001//inMFC4.2THRDCORE.CPP
#0002CWinThread:
CWinThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam)
#0003{
#0004m_pfnThreadProc=pfnThreadProc;
#0005m_pThreadParams=pParam;
#0006
#0007CommonConstruct();
#0008}
#0009
#0010CWinThread:
CWinThread()
#0011{
#0012m_pThreadParams=NULL;
#0013m_pfnThreadProc=NULL;
#0014
#0015CommonConstruct();
#0016}
#0017
#0018voidCWinThread:
CommonConstruct()
#0019{
#0020m_pMainWnd=NULL;
#0021m_pActiveWnd=NULL;
#0022
#0023//noHTHREADuntilitiscreated
#0024m_hThread=NULL;
#0025m_nThreadID=0;
#0026
#0027//initializemessagepump
#0028#ifdef_DEBUG
#0029m_nDisablePumpCount=0;
#0030#endif
#0031m_msgCur.message=WM_NULL;
#0032m_nMsgLast=WM_NULL;
#0033:
GetCursorPos(&
m_ptCursorLast);
#0034
#0035//mostthreadsaredeletedwhennotneeded
#0036m_bAutoDelete=TRUE;
#0037
#0038//initializeOLEstate
#0039m_pMessageFilter=NULL;
#0040m_lpfnOleTermOrFreeLib=NULL;
#0041}
#0042
#0043CWinThread*AFXAPIAfxBeginThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam,
#0044intnPriority,UINTnStackSize,DWORDdwCreateFlags,
#0045LPSECURITY_ATTRIBUTESlpSecurityAttrs)
#0046{
#0047CWinThread*pThread=DEBUG_NEWCWinThread(pfnThreadProc,pParam);
#0048
#0049if(!
pThread->
CreateThread(dwCreateFlags|CREATE_SUSPENDED,nStackSize,
#0050lpSecurityAttrs))
#0051{
#0052pThread->
Delete();
#0053returnNULL;
#0054}
#0055VERIFY(pThread->
SetThreadPriority(nPriority));
#0056if(!
(dwCreateFlags&
CREATE_SUSPENDED))
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- MFC 多线程程序设计 多线程 程序设计