ImageVerifierCode 换一换
格式:DOCX , 页数:18 ,大小:28.96KB ,
资源ID:8168732      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/8168732.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Delphi中的线程类.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Delphi中的线程类.docx

1、Delphi中的线程类Delphi中的线程类-之(1) Delphi中的线程类-之(1)Raptor(原作) 关键字ThreadEventCriticalSectionSynchronize Delphi中的线程类 猛禽MentalStudio (之一) Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编程的全部,我写此文的目的在于对此作一个补充。 线程本质上是进程中一段并发运行的代码。一个进程至少有一

2、个线程,即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时,就是所谓的“多线程”。 那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)。 如果用WindowsAPI来创建线程的话,是通过一个叫做CreateThread的API函数来实现的,它的定义为: HANDLECreateThread( LPSECURITY_ATTRIBUTESlpThreadAttributes, DWORDdwStackSize, LPTHREAD_START_ROUTINElpStartAddress, LPVOIDlpParameter, DWORDdwCr

3、eationFlags, LPDWORDlpThreadId ); 其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。其中的起始地址就是线程函数的入口,直至线程函数结束,线程也就结束了。 整个线程的执行过程如下图: 此主题相关图片如下:*图*因为CreateThread参数很多,而且是Windows的API,所以在CRuntimeLibrary里提供了一个通用的线程函数(理论上可以在任何支持线程的OS中使用): unsignedlong_begint

4、hread(void(_USERENTRY*_start)(void*),unsigned_stksize,void*_arg); Delphi也提供了一个相同功能的类似函数: functionBeginThread(SecurityAttributes:Pointer;StackSize:LongWord;ThreadFunc:TThreadFunc;Parameter:Pointer;CreationFlags:LongWord;varThreadId:LongWord):Integer; 这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数

5、的最大不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。 正常情况下,线程函数返回后,线程就终止了。但也有其它方式: WindowsAPI: VOIDExitThread(DWORDdwExitCode); CRuntimeLibrary: void_endthread(void); DelphiRuntimeLibrary: procedureEndThread(ExitCode:Integer); 为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如

6、在Windows中那个Handle便是这个内部Object的Handle,所以在线程结束的时候还应该释放这个Object。 虽然说用API或RTL(RuntimeLibrary)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread 使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分),如果需

7、要用到可视VCL对象,还需要通过Synchronize过程进行。关于之方面的具体细节,这里不再赘述,请参考相关书籍。 本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入研究一下TThread类的实现。因为只是真正地了解了它,才更好地使用它。 下面是DELPHI7中TThread类的声明(本文只讨论在Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码): TThread=class private FHandle:THandle; FThreadID:THandle; FCreateSuspended:Boolean; FTerminated:Boolea

8、n; FSuspended:Boolean; FFreeOnTerminate:Boolean; FFinished:Boolean; FReturnValue:Integer; FOnTerminate:TNotifyEvent; FSynchronize:TSynchronizeRecord; FFatalException:TObject; procedureCallOnTerminate; classprocedureSynchronize(ASyncRec:PSynchronizeRecord);overload; functionGetPriority:TThreadPriorit

9、y; procedureSetPriority(Value:TThreadPriority); procedureSetSuspended(Value:Boolean); protected procedureCheckThreadError(ErrCode:Integer);overload; procedureCheckThreadError(Success:Boolean);overload; procedureDoTerminate;virtual; procedureExecute;virtual;abstract; procedureSynchronize(Method:TThre

10、adMethod);overload; propertyReturnValue:IntegerreadFReturnValuewriteFReturnValue; propertyTerminated:BooleanreadFTerminated; public constructorCreate(CreateSuspended:Boolean); destructorDestroy;override; procedureAfterConstruction;override; procedureResume; procedureSuspend; procedureTerminate; func

11、tionWaitFor:LongWord; classprocedureSynchronize(AThread:TThread;AMethod:TThreadMethod);overload; classprocedureStaticSynchronize(AThread:TThread;AMethod:TThreadMethod); propertyFatalException:TObjectreadFFatalException; propertyFreeOnTerminate:BooleanreadFFreeOnTerminatewriteFFreeOnTerminate; proper

12、tyHandle:THandlereadFHandle; propertyPriority:TThreadPriorityreadGetPrioritywriteSetPriority; propertySuspended:BooleanreadFSuspendedwriteSetSuspended; propertyThreadID:THandlereadFThreadID; propertyOnTerminate:TNotifyEventreadFOnTerminatewriteFOnTerminate; end; TThread类在Delphi的RTL里算是比较简单的类,类成员也不多,类

13、属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件:OnTerminate作详细分析。 (待续) Delphi中的线程类-之(2) Delphi中的线程类-之(2)Raptor(原作) 关键字ThreadEventCriticalSectionSynchronize Delphi中的线程类 猛禽MentalStudio 之二 首先就是构造函数: constructorTThread.Create(CreateSuspended:Boolean); begin inheritedCreate; AddThread; FSuspended:=CreateSuspended; FCre

14、ateSuspended:=CreateSuspended; FHandle:=BeginThread(nil,0,ThreadProc,Pointer(Self),CREATE_SUSPENDED,FThreadID); ifFHandle=0then raiseEThread.CreateResFmt(SThreadCreateError,SysErrorMessage(GetLastError); end; 虽然这个构造函数没有多少代码,但却可以算是最重要的一个成员,因为线程就是在这里被创建的。 在通过Inherited调用TObject.Create后,第一句就是调用一个过程:AddT

15、hread,其源码如下: procedureAddThread; begin InterlockedIncrement(ThreadCount); end; 同样有一个对应的RemoveThread: procedureRemoveThread; begin InterlockedDecrement(ThreadCount); end; 它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程,而是用了InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是对变量加一或

16、减一。但它们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证执行结果正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。 以加一为例来说明二者实现细节上的不同: 一般来说,对内存数据加一的操作分解以后有三个步骤: 1、从内存中读出数据 2、数据加一 3、存入内存 现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况: 1、线程A从内存中读出数据(假设为3) 2、线程B从内存中读出数据(也是3) 3、线程A对数据加一(现在是4) 4、线程B对数据加一(现在也

17、是4) 5、线程A将数据存入内存(现在内存中的数据是4) 6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果) 而用InterlockIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个“原语”执行完毕前不会进行线程切换。所以在上面那个例子中,只有当线程A执行完将数据存入内存后,线程B才可以开始从中取数并进行加一操作,这样就保证了即使是在多线程情况下,结果也一定会是正确的。 前面那个例子也说明一种“线程访问冲突”的情况,这也就是为什么线程之间需要“同步”(Synchronize),关于

18、这个,在后面说到同步时还会再详细讨论。 说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议,个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一个是传统意义上的同步(Tooccuratthesametime),另一个是“协调一致”(Tooperateinunison)。在“线程同步”中的Synchronize一词应该是指后面一种意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不过像这样译得不准的

19、词在IT业还有很多,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的,绝不能含糊。 扯远了,回到TThread的构造函数上,接下来最重要就是这句了: FHandle:=BeginThread(nil,0,ThreadProc,Pointer(Self),CREATE_SUSPENDED,FThreadID); 这里就用到了前面说到的DelphiRTL函数BeginThread,它有很多参数,关键的是第三、四两个参数。第三个参数就是前面说到的线程函数,即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数,在这里就是创建的线程对象(即Se

20、lf)。其它的参数中,第五个是用于设置线程在创建后即挂起,不立即执行(启动线程的工作是在AfterConstruction中根据CreateSuspended标志来决定的),第六个是返回线程ID。 现在来看TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码: functionThreadProc(Thread:TThread):Integer; var FreeThread:Boolean; begin try ifnotThread.Terminatedth

21、en try Thread.Execute; except Thread.FFatalException:=AcquireExceptionObject; end; finally FreeThread:=Thread.FFreeOnTerminate; Result:=Thread.FReturnValue; Thread.DoTerminate; Thread.FFinished:=True; SignalSyncEvent; ifFreeThreadthenThread.Free; EndThread(Result); end; end; 虽然也没有多少代码,但却是整个TThread中最

22、重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明: 首先判断线程类的Terminated标志,如果未被标志为终止,则调用线程类的Execute方法执行线程代码,因为TThread是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。 所以说,Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。 如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。 最后是线程结束前做的一些收尾工作。局部变量Fr

23、eeThread记录了线程类的FreeOnTerminated属性的设置,然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。 DoTerminate方法的代码如下: procedureTThread.DoTerminate; begin ifAssigned(FOnTerminate)thenSynchronize(CallOnTerminate); end; 很简单,就是通过Synchronize来调用CallOnTerminate方法,而CallOnTerminate方法的代码如下,就是简单地调用OnTerminate事件: procedureTTh

24、read.CallOnTerminate; begin ifAssigned(FOnTerminate)thenFOnTerminate(Self); end; 因为OnTerminate事件是在Synchronize中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对Synchronize的分析)。 执行完OnTerminate后,将线程类的FFinished标志设置为True。 接下来执行SignalSyncEvent过程,其代码如下: procedureSignalSyncEvent; begin SetEvent(SyncEvent); end; 也很简单,就是设置一下一

25、个全局Event:SyncEvent,关于Event的使用,本文将在后文详述,而SyncEvent的用途将在WaitFor过程中说明。 然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些些操作,详见接下来的析构函数实现。 最后调用EndThread结束线程,返回线程返回值。 至此,线程完全结束。 (待续) Delphi中的线程类-之(3) Delphi中的线程类-之(3)Raptor(原作) 关键字ThreadEventCriticalSectionSynchronize Delphi中的线程类 猛禽MentalStudio 之三

26、 说完构造函数,再来看析构函数: destructorTThread.Destroy; begin if(FThreadID0)andnotFFinishedthen begin Terminate; ifFCreateSuspendedthen Resume; WaitFor; end; ifFHandle0thenCloseHandle(FHandle); inheritedDestroy; FFatalException.Free; RemoveThread; end; 在线程对象被释放前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设置),则调用

27、Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志,如下面的代码: procedureTThread.Terminate; begin FTerminated:=True; end; 所以线程仍然必须继续执行到正常结束后才行,而不是立即终止线程,这一点要注意。 在这里说一点题外话:很多人都问过我,如何才能“立即”终止线程(当然是指用TThread创建的线程)。结果当然是不行!终止线程的唯一办法就是让Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在Execute方法中在较短的时间内不断地检查Terminated标志,以便

28、能及时地退出。这是设计线程代码的一个很重要的原则! 当然如果你一定要能“立即”退出线程,那么TThread类不是一个好的选择,因为如果用API强制终止线程的话,最终会导致TThread线程对象不能被正确释放,在对象析构时出现AccessViolation。这种情况你只能用API或RTL函数来创建线程。 如果线程处于启动挂起状态,则将线程转入运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现,将放到后面说明。 线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。 然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减小进程的线程数。 其它关于Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面要讨论的是本文的另两个重点:Synchronize和WaitFor。 但是在介绍这两个函数之前,需要先介绍另外两个线程同步

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1