📄 chapt15.htm
字号:
<html><head><title>第十五章 多线程编程</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312"></head><body bgcolor="#00000" text="#00cc66"><p align="center"><b><font color="#FF6666" size="4">第十五章 多线程编程</font></b></p><p> 本章描述Windows 95与Windows NT的基于线程的多任务设计。<br> <b>15.1 线 程</b><br> 我们知道,Windows 95支持两种形式的多任务。第一种类型是基于进程的机制,这也是Windows从一开始就支持的多处理类型。进程本质上是指正在执行着的程序,在基于进程的多任务环境下,两个至多个进程可以并发地执行。第二种类型是基于线程(thread)的机制,基于线程的多任务对于多数Windows用户和程序员而言是一个崭新的概念,因为Windows 95以前的Windows版本不能支持线程的概念(Windows NT除外)。<br> 线程是指进程中的一个执行流。多个线程可以并发地运行于同一个进程中。<br> 在Windows 95/98/NT中,每一个进程拥有至少一个线程,允许同时执行两个或多个线程。并由我们的程序控制它们。基于线程的多任务允许同一程序的不同部分(线程)并发地执行。这样,程序员就能够写出非常高效的程序,因为程序员能够定义执行线程并管理程序的执行方式,能够完全地控制程序片段的执行。例如,可以在一个程序中指定一个线程执行文件排序工作,指定另外一个线程负责收集来自某个远程资源的信息,指定又一线程完成用户输入的工作。因为处于多线程多任务环境,每一线程都能够并发地执行,这样就能充分地利用CPU时间。 <br> 通常,多线程处理使程序运行速度减慢,除非我们有多线程CPU,以及可以在处理器中分离线程的操作系统。<br> 基于线程的多任务使得同步功能特征显得更为重要。既然多个线程(及进程)可以并发执行,那么必须适当地协调线程间的执行顺序以使其能同步访问共享资源与内存,从而使程序编写起来显得更加复杂。Windows 95增加了一个完整的子系统以支持同步机制,其中的一些关键特征将在本章后进一步地讨论。<br> <b>15.2 线 程 类</b><br> 所有进程至少都拥有一个执行线程,为了讨论方便,我们称该执行线程为主线程。然而,在一个程序中,除了主线程之外,可能还创建有一个或更多的执行线程。通常,一个线程一旦创建即开始执行。因此,一个进程最开始作为一个执行线程而被启动,以后它还可以创建更多的执行线程。通过这种机制,基于线程的多任务得以实现。<br> 编写基于线程的程序,既可以使用Windows 95提供的Win32应用程序接口函数(API),也可以使用Delphi 5.0提供的线程对象。而使用Delphi 5.0的线程对象(TThread对象),则可使编写线程程序变得简单、高效。TThread对象提供了许多特性和方法(成员函数),你只要根据工作需要对这些函数或方法进行重写,即可在程序中实现多线程机制。<br> Tthread类是TObject对象的直接派生类。与其它大多数Delphi类和构件不同的是,你不能在程序中直接使用该对象,而必须从Tthread类产生一个新的派生类,并对需要使用的方法进行重写,以重载(Override)Tthread类的方法。下面对Tthread类的部分特性和方法进行讨论(除非特别说明,所有讨论的特性和方法都是Public):<br> <b>15.2.1 线程类特性</b><br> 线程类包括以下几种特性:<br> 1.FreeOnTerminate特性:该特性为布尔类型,只能在运行时间使用。 该特性决定线程结束时,是由VCL自动消除(destroy)线程对象(值为True),还是你自己负责消除线程对象(值为False)。FreeOnTerminate缺省值为 False。<br> 2.Thandle特性:每一个执行线程都有一个句柄,Win32 API大多数线程函数都需要用到句柄。该特性就用于保存线程的句柄,通过读取Thandle特性的值,可以获得线程的句柄,从而可调用Win32 API函数来执行控制线程的操作。<br> 3.Priority特性:该特性动态设置线程调度的优先级,只能在运行时间使用。优先级的缺省值为tpNormal,但你可以根据需要进行设置,以改变线程调度的优先级。详细介绍请看12.3节。<br> 4.ReturnValue特性:该特性为整数类型,且只能由线程对象的派生类调用或访问(Protected特性)。该特性等价于函数的Result变量,你可以使用该特性来指示线程执行的成功或失败。<br> 5.Suspended特性:该特性为布尔类型,用于决定线程是否挂起。被挂起的线程停止执行,直到该线程被恢复。设置线程的Suspended特性为TRUE,可以挂起一个线程;设置线程的Suspended特性为FALSE,可以恢复一个线程的执行。<br> 6.Terminated特性:该特性为布尔类型,且为Protected特性,只读,只能由线程对象的派生类调用或访问。该特性决定线程是否应该中止执行。如果该特性值为TRUE,表示线程执行即将结束,这时程序应立即退出线程的Execute方法。你也可以通过调用Terminate方法来强行设置线程的Terminated特性为TRUE,从而达到退出线程执行的目的。<br> 7.ThreadID特性: 该特性用于保存线程的标识,有些Win32 API线程函数需要使用该标识。通过读取ThreadID特性的值,可以获得线程的标识,从而可调用Win32 API函数来执行其它控制线程的操作。<br> <b>15.2.2 线程类方法</b><br> 线程类主要包括以下几种方法:<br> 1.Create构造函数:参数CreateSuspended用来选择是马上启动线程(值为False)还是暂停它(值为True)。<br> 2.DoTerminate过程:该过程只能由线程对象内部方法调用,用于与主VCL线程的同步,并产生OnTerminate事件。一般来说,当线程终止时,线程会自动调用DoTermiante过程,程序员一般不需要重写该过程的代码。<br> 3.Execute过程:该方法开始一个线程的执行,你必须在派生的线程类中重写该过程,以实现线程的功能。当使用Windows API时,这段代码应该放置在线程函数中。Execute方法返回时,终止线程的执行,释放线程堆栈,并调用OnTerminate事件驱动程序(如果有的话)。Execute方法必须周期性地检测Terminated特性,如果为TRUE,Execute方法必须立即返回。如果线程返回失败,说明调用Termianted方法时,线程没有终止。<br> 4.Resume过程:该方法恢复一个被挂起的线程的执行。Resume方法调用可以嵌套,如果你执行了多次Suspend,则线程实际恢复运行前,你必须执行同样次数的Resume调用。<br> 5.Suspend过程:该方法暂停一个线程的执行。暂停执行的线程可以调用Resume方法来恢复执行。Suspend方法调用可以嵌套,如果你执行了多次Suspend,则线程实际恢复运行前,你必须执行同样次数的Resume调用。<br> 6.Synchronize过程:在Delphi 5.0的多线程编程中,各种VCL构件都是临界资源,只能由主线程使用。其它线程要使用这些VCL构件,必须使用Synchronize方法,通过传递使用了VCL构件的方法,就可避免多线程与VCL构件的冲突,避免重入问题。该过程带有唯一一个TThreadMethod类型的参数是一个不接收参数的对象方法,用于指定在线程对象中的方法。<br> 7.Terminate过程:该方法设置Terminated特性为TRUE,并通知你的线程,线程执行将终止。Execute方法必须周期性地检测Terminated特性,如果为TRUE,必须立即返回。<br> 8.WaitFor函数:该方法等待线程执行的终止,然后返回ReturnValue特性的值(整型)。如果线程不终止,WaitFor函数就不会返回,因此,在调用WaitFor函数后,必须确保线程退出,这可以通过终止Execute方法的执行或当Terminated特性值为TRUE时退出来实现。另外,如果线程使用了Synchronize方法,则不要在主线程中使用WaitFor方法,因为这样一来易引起死锁,或导致各种Ethread例外的发生。Synchronize在主线程允许同步的方法执行之前一直等待,直到主线程进入消息循环,如果主线程调用了WaitFor,它就不能进入消息循环,Synchronize也不会返回,TThread会检查到这种错误,从而引起Ethread例外。如果WaitFor已被调用,而Synchronize又一直等待,则TThread不会检查到这种错误,你的程序将进入死锁。<br> 9.OnTerminate过程:该过程是OnTerminate事件的驱动程序。OnTerminate事件发生在线程的Execute方法已经返回,TThread结束线程之前。该事件驱动程序只能在主线程使用,可以调用各种VCL方法和特性。<br> 15.3 创建多线程程序 <br> 下面以一个例子说明创建多线程执行程序的过程。在图15.1中,程序将创建两个执行5000次的循环的线程。每个线程在执行循环的每一次叠代后,将显示循环的次数。当运行该程序时,会发现两个线程是并发执行的。<br> <b>15.3.1 创建多线程</b><br> 在Delphi中,你即可以通过直接书写线程代码来创建线程,也可以Delphi的File|New命令向当前项目文件加入一个线程对象来实现。两者结果都一样,都是产生一个Tthread类的派生类。下面就使用后一种方法进行说明。<br> 选择File菜单的New命令,打开New Items对话框,选择New页标签下的Thread Object图标(见图15.2),打开New Thread Object对话框,在New Thread Object对话框的Class Name编辑框,输入创建线程的类名,然后选择OK按钮,则Delphi生成一个新的代码文件,该代码文件即为新线程类的代码文件。<br> 程序如下:<br> unit Unit2;<br> interface<br> uses Classes;<br> type <br> MyThread = class(TThread) <br> private<br> { Private declarations } <br> protected procedure Execute;<br> override;<br> end;<br> implementation<br> { Important: Methods and properties of objects in VCL can only be used <br> in a method called using Synchronize, for example, <br> Synchronize(UpdateCaption); and UpdateCaption could look like, <br> procedure MyThread.UpdateCaption; <br> begin <br> Form1.Caption := 'Updated in a thread';<br> end; }<br> { MyThread }<br> procedure <br> MyThread.Execute;<br> begin <br> { Place thread code here }<br> end;<br> end.<br> 该代码文件生成一个线程类的派生类,并提供了需要派生类覆盖的方法Execute说明。文件中还包含如何编写处理VCL构件方法的说明信息。现根据本程序的需要,修改代码文件,并将其保存为ThreadUnit.Pas。<br> interface<br> uses Classes, StdCtrls,SysUtils;<br> type <br> MyThread = class(TThread) <br> private <br> { Private declarations } <br> AEdit:TEdit; MaxLoop:Integer; CurrentLoop: Integer; <br> protected <br> procedure Execute; override;<br> procedure DisLoop; <br> public <br> constructor Create(Edit:TEdit;Max:Integer);<br> end;<br> implementation<br> { MyThread }<br> constructor MyThread.Create(Edit:TEdit;Max:Integer);<br> begin <br> inherited Create(False);<br> AEdit:=Edit; <br> MaxLoop:=Max;<br> FreeOnTerminate := True;<br> end;<br> procedure MyThread.DisLoop;<br> begin <br> AEdit.text:=InttoStr(CurrentLoop);<br> end;<br> procedure MyThread.Execute;<br> var I:Integer;<br> begin <br> for I:=0 to MaxLoop Do <br> begin <br> CurrentLoop:=I; <br> Synchronize(DisLoop); <br> if Terminated then Exit; <br> end;<br> end;<br> end. <br> 下面就有关问题进行说明:<br> 1.线程类中增加的AEdit,MaxLoop,CurrentLoop三个用于控制循环次数的显示,由于每个线程的这三个特性都不相同,且都需要访问VCL构件,因此它们都设置为私有特性,不能被继承,也不能在线程类外使用,当类实例化时后,这些特性的值由Create拷入并初始化。 <br> 2.增加的DisLoop方法用于向编辑框构件写入循环次数。Execute方法通过使用Synchronize调用DisLoop,从而解决了使用VCL构件时多线冲突问题。<br> 3.构造函数Create覆盖了原线程类的构造函数,并将传入的参数Edit(对应的编辑框件)和Max(最大循环次数)赋给实例化的对象。<br> 4.线程一旦创建,就执行Execute方法,每循环一次,就将循环次数显示在编辑框.<br> <b>15.3.2 启动线程</b><br> 本程序中,用户通过单击Start按钮来开始两个线程的执行,下面是单击Start按钮的事件驱动程序:<br> procedure TParaComupte.StartButtonClick(Sender: TObject);<br> begin <br> ThreadsRunning:=2; <br> MyThread1:=MyThread.Create(Edit1,5000); <br> MyThread1.OnTerminate := ThreadDone; <br> MyThread2:=MyThread.Create(Edit2,5000); <br> MyThread2.OnTerminate := ThreadDone; <br> StartButton.Enabled := False;<br> end;<br> 该事件驱动程序创建两个线程,并传入有关参数,ThreadsRunning全局变量用于说明创建线程的个数,ThreadDonee用于定义当线程执行结束时(发生OnTerminate事件)执行的过程,其代码为:<br> procedure TParaCompute.ThreadDone(Sender: TObject);<br> begin <br> dec(ThreadsRunning); <br> if ThreadsRunning=0 then <br> StartButton.Enabled := True;<br> end;<br> 每结束一个线程,ThreadsRunning就减1,当ThreadsRunning值为0时,表示线程全部执行完毕,Start按钮可以接收用户输入。<br> <b>15.3.3 线程的暂停、恢复与终止</b><br> 为了说明问题,本节在窗体中增加下列按钮构件Suspend、Resume、Terminate(见图15.4)。Suspend暂停线程1的执行,Resume恢复暂停了的线程,Terminate则终止线程的执行。<br> 在线程类一节,我们介绍了Suspended特性。该特性为布尔类型,用于决定线程是否挂起。设置线程的Suspended特性为TRUE,可以挂起一个线程;设置线程的Suspended特性为FALSE,可以恢复一个线程的执行。而执行Terminate过程,则可设置Terminated特性为TRUE,并通知你的线程,线程执行将终止。<br> 如MyThread1.Suspended:=True;<br> <b>15.4 线程的优先级</b><br> 每一个线程与一个优先级相联。线程优先级用于确定一个线程收到多少CPU时间。线程类的Priority特性用于获取和设置线程调度的优先级。优先级的缺省值为tpNormal,但你可以根据需要进行设置,以改变线程调度的优先级。Delphi的TThreadPriority枚举类型定义了Priority特性所有可能的值。这些值包括:<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -