您好,欢迎访问三七文档
软件工具与环境第二章多任务与多线程编程一、Windows程序的生与死在了解Windows程序的架构以及它与Windows系统之间的关系后,对Windows消息种类以及产生时机的透彻了解,正是程序设计的关键。下面以窗口的产生与死亡,说明消息的产生与传递,以及应用程序的生与死。1、程序初始化过程中调用CreateWindow,为程序建立了一个窗口,作为程序的屏幕舞台。CreateWindow产生窗口之后会送出WM_CREATE直接给窗口函数,后者于是可以在此时做些初始化操作(例如配置内存、打开文件、读初始数据等。2、在程序活着的过程中,不断以GetMessage从消息队列中获取消息。如果这个消息是WM_QUIT,GetMessage会返回0而结束while循环,进而结束整个程序。3、DispatchMessage通过WindowsUSER模块的协助和监督,把消息分发给窗口函数。消息将在该处被判别并处理。4、程序不断进行上述2和3的操作。5、当操作者按下系统菜单中的Close命令项时,系统送出WM_CLOSE。通常程序的窗口函数不拦截此消息,于是DefWindowProc处理它。6、DefWindowProc收到WM_CLOSE后调用DestroyWindow把窗口清除。DefWindowProc本身又会送出WM_DESTROY。7、程序对MW_DESTROY的标准反应是调用PostQuitMessage。8、PostQuitMessage没有什么操作,只送出WM_QUIT消息,准备让消息循环中的GetMessage获取此消息,结束消息循环。二、空闲时间的处理1、空闲时间的概念所谓空闲时间(idletime)是指“系统中没有任何消息等待处理”的时间。例如没有任何程序使用定时器(timer,它会定时送来WM_TIMER),使用者也没有按键盘和鼠标或操作任何外设,那么系统就处于所谓的空闲时间。2、空闲时间的处理空闲时间经常发生。后台工作最适合在空闲时间完成。传统的SDK程序如果要处理空闲时间,可以用下列循环取代WinMain中的传统消息循环(见图1)原因是PeekMessage和GetMessage的性质不同:它们都是从消息队列中获取消息,如果没有消息,程序的主执行线程(primarythread,是一个UI执行线程)会被操作系统挂起。当操作系统再次回来照顾这一执行线程时,发现消息队列中仍然是空的,这时两个函数的行为就不同了:(1)GetMessage会过门不入,于是操作系统再去照顾其它程序。(2)PeekMessage会取回控制权,使程序得以执行一段时间。于是上述消息循环进入OnIdle函数中。三、Windows的多任务1、Windows3.X的协同式多任务Windows3.X都允许同时执行多个程序。但分享CPU是程序的责任(即应用程序具有对CPU的控制权)。如果有一个程序不放弃CPU(许多程序采用传统的消息循环,而拒绝与其它程序共享资源),其它程序只有挂起而无法响应操作。2、Windows9X的抢先式多任务操作系统能够强迫应用程序把CPU分享给其它程序。即程序对CPU的占用时间由系统控制,系统为每个程序分配一定的CPU时间,当程序的运行超过规定时间后,系统就会中断该程序并把CPU控制权让给其它程序。3、多线程多任务在Win32系统中,执行一个程序,必然会产生一个进程;当一个进程建立后,主线程也产生了。多任务是指系统可以同时运行多个进程,而每个进程也可同时执行多个线程。一个程序可以运行多个线程,每个线程独立地执行程序代码中的一组语句。四、进程与线程通常用进程(process)表示一个执行中的程序,并认为它是CPU调度单位。事实上线程(thread)才是调度单位。进程是应用程序的运行实例。每个进程都有自己私有的虚拟地址空间。每个进程都有一个主线程,但可以建立另外的线程。进程中的线程是并行执行的,各线程占用CPU的时间由系统决定。线程是Windows9X/NT系统调度分配CPU时间的基本单位。进程至少有一个线程,也可以另外增加线程。系统为每个线程分配一个CPU时间片(约20ms),系统不停地在各个线程之间切换。线程只有在分配的时间片内才有对CPU的控制权1、核心对象核心对象是系统的一种资源,系统对象一但产生,任何应用程序都可以开启并使用该对象。系统给予核心对象一个计数值作为管理之用。核心对象包括下列几种:(见图2)这些核心对象的产生方式(使用的API)不同,但都会获得一个handle作为识别;每被使用一次,其对应的计数值就增1。核心对象的结束方式相当一致,调用CloseHandle即可。Process对象不是用来执行程序代码的;它只是一个数据结构,系统用它来管理进程。程序代码的执行是线程的工作。2、进程的产生和死亡执行一个程序,必然会产生一个进程(process)。最直接的程序执行方式就是在shell(如Windows9X的资源管理器)中用鼠标双击某一个可执行文件图标(如为app.exe),运行起来的app进程其实就是shell调用CreateProcess激活的。整个执行流程如下:(1)shell调用CreateProcess激活app.exe(2)系统产生一个进程核心对象,计数值为1。(3)系统为此进程建立一个4GB地址空间。(4)加载器将必要的码加载到上述地址空间中,包括app.exe的程序、数据,以及所需的动态链接库(dlls).(5)系统为此进程建立一个线程,称为主线程(primarythread)。线程才是CPU时间的分配对象。(6)系统调用CRuntime函数库的Startupcode。(7)Startupcode调用app程序的WinMain函数。(8)app程序开始运行。(9)操作者关闭app主窗口,使WinMain中的消息循环结束,于是WinMain结束。(10)回到Startupcode。(11)回到系统,系统调用ExitProcess结束进程。因此通过这种方式执行的所有Windows程序,都是shell的子进程。但shell在调用CreateProcess时已经剪断了父进程与子进程之间的关系,而使它们成为独立个体。可以写一个程序,专门用来激活其它的程序。关键在于我们能否正确运用具有众多参数的API函数CreateProcess(图3)如果一个进程要结束自己,只要调用:VOIDExitProcess(UINTfuExitCode)如果说一个进程要结束另一个进程,可以使用:(图4)显然,只要有某个进程的handle,就可以结束它。前面曾提过剪断父子进程关系的概念,只要我们在父进程结束前,把它所开启的核心对象(如子进程、线程对象等)用CloseHandle关闭,就可以实现此目的,操作系统会自动把对象的计数值减1,表示与此核心对象不再有任何关系(图5)。如果计数值为0,对象会自动被操作系统摧毁。3、一个线程的产生与死亡执行程序代码是线程的工作。当一个进程建立后,主线程也产生了。所以每一个Windows程序一开始就有一个线程。我们可以调用CreateThread产生额外的线程,系统会帮我们完成下列事情:(1)配置线程对象,其handle将成为CreateThread的返回值。(2)设定计数值为1。(3)配置线程的context。(4)保存线程的堆栈。(5)将context中的堆栈指针和指令指针设定妥当。如果要在程序中产生一个新线程,调用CreateThread(图6)即可。(例1、例2、例3)当完成工作后,应该调用CloseHandle释放核心对象(图7)。(例4)使用“线程核心对象”的线程会使核心对象开启。因此线程对象的默认计数值为2。当调用CloseHandle()时,计数值减1;当线程对象结束时,计数值再减1。只有当这两件事都有发生了,对象才会被真正清除。线程的结束有两种情况:(1)一种是正常结束这种情况是线程函数结束退出,线程也就自然结束了(例5)。这时系统会调用ExitThread(图8)做些清理工作;当然线程自己也可以自行调用此函数来结束自己。(例6)在下面的程序中将CloseHandle函数与GetExitCodeThread函数的关系作了调整:CloseHandle的调用操作被移到了GetExitCodeThread的前面,目的是使GetExitCodeThread的调用失败(例7)。前面使用了两种等待方法。1)第一种方法是Win32Sleep()函数这个函数要求操作系统暂时中止线程的动作,直到经过某个指定的时间后才能恢复。实际上,程序员不可能知道具体应该要等待多少时间。2)第二种方法是使用循环等待,不断调用GetExitCodeThread(),直到其结果不再是STILL_ACTIVE。这种方法对系统效率会造成冲击。因此,绝对不要在Win32中使用这种循环的方法。(例8)3)Win32提供一种等待一个对象的方法:WaitForSingleObject()函数(图9)(例9)4)Win32也提供一种同时等待一个以上对象的函数WaitForMultiplebject()(图10)(例10)(2)非正常结束一是进程结束(自然也导致线程的结束)。二是另外的线程以TerminateThread将它强制结束。不过TerminateThread少用为好。4、线程的种类MFC将线程分为工作者线程和用户界面线程。(1)工作者线程(WorkerThreads)工作者线程与用户界面无关,没有消息循环,一般用来完成后台工作。(2)用户界面线程(UIThreads)用户界面线程与用户界面有关,具有消息循环来处理界面消息,可与用户进行交互。五、MFC多线程程序设计1、产生一个工作者线程由于工作者线程与用户界面无关。编程时,应该准备一个线程函数,然后调用AfxBeginThread全局函数:(图11)(例11、例12、例13)2、产生一个用户界面线程用户界面线程不能只用一个线程函数来代表,因为它要处理消息循环。CWinThread::Run()成员函数中就有一个消息循环。所以应该先从CWinThread类中派生一个自己的类,再调用全局函数AfxBeginThread产生一个CWinThread类的对象:(图12)3、线程的结束工作者线程的生命就是线程函数本身,函数一旦return,线程也就结束。或者线程函数也可以调用AfxEndThread,结束线程。用户界面线程因为有消息循环,必须在消息队列中放一个WM_QUIT,才能结束线程。放置消息WM_QUIT的方式和一般的Win32程序一样,调用PostQuitMessage即可办到;或者在线程的任何一个函数中调用AfxEndThread,也可以结束线程。
本文标题:软件工具与环境
链接地址:https://www.777doc.com/doc-923529 .html