收藏 分销(赏)

Delphi编程语言多线程详解.pdf

上传人:曲**** 文档编号:8987017 上传时间:2025-03-10 格式:PDF 页数:40 大小:2.36MB 下载积分:12 金币
下载 相关 举报
Delphi编程语言多线程详解.pdf_第1页
第1页 / 共40页
Delphi编程语言多线程详解.pdf_第2页
第2页 / 共40页


点击查看更多>>
资源描述
Edited by Foxit Reader 八1.烈郎潞歇s解的刚惜第11章编写多线程应用程序本章内容:对线程的解释 TThread 对象 管理多线程 一个多线程的示范程序 多线程与数据库 多线程与图形处理Win32操作系统提供了在应用程序中执行多线程的能力。从16位的Windows升级到Win32的一个最 大受益便是:它允许多线程同时运行。这也是要升级到32位Delphi的一个最主要的原因。本章提供了 程序中如何进行多线程编程的所有细节。11.1对线程的解释如同在第3章“Win32 API”中所讨论过的,线程是一种操作系统对象,它表示在进程中代码的一 条执行路径。在每一个Win32的应用程序中都至少有一个线程,它通常被称为主线程或默认线程。在 应用程序中也可以自由地创建别的线程去执行其他任务。线程技术使不同的代码可以同时运行。当然,只有在多CPU的计算机上,多个线程才能够真正地 同时运行。然而,由于操作系统把CPU的时间分成很短的片段分配给每个线程,这样给人的感觉好像 是多个线程真的同时运行。提示线程不能也从来没有被16位的Windows支持。这就意味着,任何32位版本的Delphi的 多线程程序代码都有无法在Delphi 1环境下编译。如果你在为这两个平台开发程序,请一定记 住这一点。11.1.1 一种新型的多任务线程的概念与16位环境中的多任务有很大的不同。或许曾听人们这样讲:Win32是一种抢占式操 作系统,而Windows 3.1是一种协作式的多任务环境。其关键区别在于:在抢占式多任务环境中,操作系统负责管理哪个线程在什么时候执行。如果当 线程1暂停执行时,线程2才有机会获得CPU时间,我们说线程1是抢占的。如果某个线程的代码陷入 死循环,这并不可怕,操作系统仍会安排时间给其他线程。在Windows 3.1下,程序员必须保证应用程序能够把控制权返还给Windows。如果这一步失败,将 导致整个操作环境锁死,或许你已经有过这样的痛苦经历。只要稍微想想便会明白,16位的Windows 是如此脆弱,它依赖于应用程序的运行情况,并且不允许程序陷入死循环或无穷递归以及任何封闭状 态。这是因为所有的应用程序都必须协助Windows工作,这种工作类型被称为协作式多任务系统。11.1.2 在Delphi程序中使用多线程对一个Windows程序员来说,线程提供了非常大的好处。可以在应用程序中的任何地方创建多个第ii章编写多线程应用程序3 05附属线程,它们在后台进行各种类型的处理。例如:在一个电子表格程序中计算单元格,或是脱机打 印Word文档。即使后台正在处理许多工作,也不会影响前台的用户界面。大多数VCL在被设计时,都只考虑了在任何时刻只有一个线程来访问它。其局限性尤其体现在 VCL的用户界面部分。同时,一些非用户界面部分也不是线程安全的。1.非用户界面的VCL实际上VCL只有很少的部分保证是线程安全的。可能在这很少的部分中,最让人注意的是VCL的 属性流机制。VCL的流机制确保了组件流能被多线程安全地读写。请记住即使最基础的VCL类(诸如 TList),也不是为安全地同时操作多个线程而设计的。对某些情况,VCL提供了一些线程安全的替代,比如,用TThreadList来替代TList可以解决多个线程操作的问题。2.用户界面的VCLVCL要求所有的用户界面控制要发生在一个应用程序的主线程的环境中(线程安全的TCanvas类除 外,本章后面就此将说明)。当然,利用技术手段是可以有效地利用附属线程更新用户界面的(后面将 会讨论)。本章的例子将介绍一些在Delphi程序中使用多线程的方法。11.1.3关于线程的滥用好事太多也可能会是件坏事,线程正是这样。从程序设计的角度看,尽管线程能帮助我们解决一 些问题,但同时它又带来新的问题。例如,在编写一个集成开发环境时,也许想使编译器在一个专门 的线程中运行,这样才能使你在编译时照常做其他的工作。可问题是,如果正在编译时修改了一个文 件,会出现什么情况呢?为解决这个问题,你可以临时复制那个文件的副本,或者干脆禁止用户在编 译期间修改尚未编译的文件。这说明,线程可以解决一些问题,但同时也带来了其他问题。一个更为 严重的问题是多线程依赖于时间片,在多线程中的缺陷非常难以调试。同时,编写和实现线程安全的 代码也比较困难,程序员需要考虑很多的因素。11.2 TThread 对象Delphi把有关线程的API封装在TThread这个Object Pascal的对象中。虽然TThread已经封装了几 乎所有与线程有关的API。但在某些情况下,尤其在处理线程同步的问题时,仍需调用一些别的API函 数。本节将介绍TThread用法。11.2.1 TThread 基础下面是TThread在Classes单元中的声明:typeTThread=classprivateFHandle:THandle;FThreadID:THandle;FTerminated:Boolean;FSuspended:Boolean;FFreeOnTerminate:Boolean;FFinished:Boolean;FReturnValue:Integer;FOnTerminate:TNotifyEvent;FMethod:TThreadMethod;FSynchronizeException:TObject;procedure CallOnTerminate;function GetPriority:TThreadPriority;Edited by Foxit Reader2/j Copyright(C)by FoxitJ UO 第二部分高级掾庐valuation Only.procedure SetPriority(Value:TThreadPriority);procedure SetSuspended(Value:Boolean);protectedprocedure DoTerminate;virtual;procedure Execute;virtual;abstract;procedure Synchronize(Method:TThreadMethod);property Returnvalue:Integer read FReturnValue write FReturnValue;property Terminated:Boolean read FTerminated;publicconstructor Create(CreateSuspended:Boolean);destructor Destroy;override;procedure Resume;procedure Suspend;procedure Terminate;function WaitFor:Integer;property FreeOnTerminate:Boolean read FFreeOnTerminate write FFreeOnTerminate;property Handle:THandle read FHandle;property Priority:TThreadPriority read GetPriority write SetPriority;property Suspended:Boolean read FSuspended write SetSuspended;property ThreadID:THandle read FThreadID;property OnTerminate:TNotifyEvent read FOnTerminate write FOnTerminate;end;从声明中可以看出,TThread是直接从TObject继 承下来的,因此,它不是组件。其中,Execute。是抽 象的,说明TThread类是抽象的。睇神婺,不能创建 TThread的实例,而只能创建其遇属每例。可以选 择Delphi的主菜单中的File|New命令,然后在New Items对话框中双击Thread Objecto New Items对话框 如图11-1所示。当双击Thread Object后,将出现一个对话框,它 会提示输入线程对象的名称。例如,输入TTestThread。Delphi会自动创建一个包括新创建的线程对象的单元,如下所示:typeTTestThread=class(TThread)private Private declarations protectedprocedure Execute;override;end;图11-1在New Items对话框 中的Thread Object项正如你看到的一样,TThread的派生类中唯一必须覆盖的方法是Execute。假设你要在 TTestThread中进行复杂的计算,可以如下定义Execute。:procedure TTestThread.Execute;var i:integer;beginfor i:=1 to 2000000 doinc(Answer,Round(Abs(Sin(Sqrt(i);end;当然,本段代码只是为了演示一个长循环。本线程的唯一目的是解决长时间的计算工作。Add Edited by Foxit ReaderLrlind*D(jbiCOM Copyright(C)by Foxit Software Company,空物印8 卡*U F V For Evalu通皿窜il编写多线程应用程序 JU/现在,可以通过调用线程对象的Create。使上述代码执行。在下例中,看到在主窗体上有一个按钮,单击此按钮就会调用Create()(请注意,不要忘记在主窗体单元的uses子句中包含TTestThread单元,否 则会导致编译错误)。procedure TForml.Button1Click(Sender:TObject);var NewThread:TTestThread;begin NewThread:=TTestThread.Create(False);end;如果运行此程序并单击按钮,上述的那个长循环就会执行,但同时,你会注意到仍然可以操纵窗 口,做各种其他操作。注意当TThread的Create。被调用时,需要传递一个布尔型的参数CreateSuspended。如果把这 个参数设成False,那么当调用Create。后,Excute()会被自动地调用,也就是自动地执行线程代 码。如果该参数设为True,则需要运行TThread的Resume。来唤醒线程。一般情况下,当你调 用Create。后,还会有一些其他的属性要求设置。所以,应当把CreateSuspended参数设为True,因为在TThread已执行的情况下设置TThread的属性可能会引起麻烦。再深入一点讲,在构造函数Create。中隐含调用了一个RTL例程BeginThread(),而它又调用 了 一个API函数CreateThread()来创建一个线程对象的实例。CreateSuspended参数表明是否传递 CREATE_ SUSPEDED标志给CreateThread()。11.2.2 TThread实例回到TTestThread对象的Excute。,我们注意到它声明了一个局部变量i,试想一下,当存在两个 TTestThread对象的实例时,i的值会怎么样呢?它会覆盖掉另一个实例的值吗?或者第一个实例具有优先 级?回答是否定的。因为Win32会为每个线程都分配一个单独的栈。这意味着,当你创建了TTestThread 对象的多个实例后,在每个线程实例所在的栈里都有一份i的副本。因此,所有的线程都是独立地运行 的。然而,上述内容并不适合于线程中的全局变量。在本章的11.3节中我们将讨论此问题。11.2.3 线程的终止当线程对象的Excute()执行完毕,我们就认为此线程终止了。这时,它会调用Delphi的一个标准例 程EndThread(),这个例程再调用API函数ExitThread()o由ExitThread。来清除线程所占用的栈。当结束使用TThread对象时,应该确保已经把这个Object Pascal对象从内存中清除了。这才能确保 所有内存占有都释放掉。尽管在进程终止时会自动清除所有的线程对象,但及时清除已不再用的对象,可以使内存的使用效率提高。利用将FreeOnTerminate的属性设为True的方法来及时清除线程对象是最 方便的办法,这只需要在Excute()退出前设置就行了。设置方法如下:procedure TTestThread.Execute;var i:integer;begin FreeOnTerminate:=True;for i:=1 to 2000000 do inc(Answer,Round(Abs(Sin(Sqrt(i);end;这样,当一个线程终止时,就会触发OnTerminate事件,就有机会在事件处理过程内清除线程对Edited by Foxit Reader 4 4308第二部分高级潍懈公胪弱愤也即驱-1下载I 象了。提示OnTerminate事件是在主线程的环境中发生的。这意味着,在处瞿这个事件的处理过程中,你可以不需要借助于SynchrocizeO而 自 由地访问 VCL。什么 意思?要记住Execute。需要经常地检查Terminated属性的值,来确认是否要提前退出。尽管这将意味着 当使用线程工作时,你必须关心更多的事情,但它能确保在线程结束时,能够完成必要的清除。下面 是一段在Execute。增加处理操作的简单代码:procedure TTestThread.Execute;vari:integer;beginFreeOnTerminate:=True;for i:=1 to 2000000 do beginif Terminated then Break;inc(Answer,Round(Abs(Sin(Sqrt(i);end;end;注意 某些紧急情况下,你可以使用Win32 Api函数TerminateThread()来终止一个线程。但是,除非没有别的办法了,否则不要用它。例如,当线程代码陷入死循环时。TerminateThread。的 声明如下:function TerminateThread(hThread:THandle;dwExitCode:DWORD);TThread的Handle属性可以作为第一个参数,因此,TerminateThread。常这样调用:TerminateThread(MyHosedThread.Handle,0)如果选择使用这个函数,应该考虑到它的负面影响。首先,此函数在Windows NT与在 Windows 95/98下并不相同。在Windows 95/98下,这个函数能够自动清除线程所占用的栈;而 在Windows NT下,在进程被终止前栈仍然保留。其次,无论线程代码中是否有try.finally块,这个函数都会使线程立即停止执行。这意味着,被线程打开的文件没有被关闭、由线程申请的 内存没有被释放等情况。而且,这个函数在终止线程的时候也不通知DLL,当DLL关闭时,这 也容易出现问题。在第9章“动态链接库”中有关于这方面的更多内容。11.2.4 与VCL同步如同在前面多次提到的,对VCL的访问只能在主线程中。这将意味着:所有需要与用户打交道的 代码都只能在主线程的环境中执行。这是其结构上明显的不足,并且这种需求看起来只局限在表面上,但它实际上有一些优点。1.单线程用户界面的好处首先,只有一个线程能够访问用户界面,这减少了编程的复杂性。Win32要求每个创建窗口的线 程都要使用GetMessage。建立自己的消息循环。正如你所想的,这样的程序将会非常难于调试,因为 消息的来源实在太多了。其次,由于VCL只用一个线程来访问它,那些用于把线程同步的代码就可以省略了,从而改善了 应用程序的性能。2.Synchronize。方法在TThread中有一个方法叫Synchronize。,通过它可以让线程的一些方法在主线程中执行。Synchronize()的声明如下:procedure Synchronize(Method:TThreadMethod);俨M.h fj =上品 Edited by Foxit ReaderLrllh94U(jbiC0M Copyright(C)by Foxit Software Company,缪物裨 8不贰怛 V For Evalu通四副I踹写多线程应用程序参数Method的类型是TThreadMethod类型(这是一个无参数的过程),类型声明如下:typeTThreadMethod=procedure of object;Method参数用来传递要在主线程中执行的方法。以TTestThread对象为例,如果要在一个编辑框中 显示计算的结果。首先要在TTestThread中增加能对编辑控件的Text属性进行修改的方法,然后,用 Synchronize。来调 用 止匕方 法。我们给这个方法取名为GiveAnswer()。在清单11-1中列出了例子的代码,其中包含了更新主窗体 的编辑控件的代码。清单 11-1 ThrdU.PAS 单元unit ThrdU;interfaceuses Classes;typeTTestThread=class(TThread)private Answer:integer;protected procedure GiveAnswer;procedure Execute;override;end;implementation uses SysUtils,Main;TTestThread)procedure TTestThread.GiveAnswer;beginMainForm.Editl.Text:=InttoStr(Answer);end;procedure TTestThread.Execute;varI:Integer;beginFreeOnTerminate:=True;for I:=1 to 2000000 do begin if Terminated then Break;Inc(Answer,Round(Abs(Sin(Sqrt(I);Synchronize(GiveAnswer);end;end;end.你已经知道Synchronize。的作用是在主线程中执行一个方法。但是,你或许已把Synchronize。当 成一个黑匣子,不清楚它是如何工作的。如果愿意揭开这个谜,请继续向下读。当你在程序中第一次创建一个附属线程时,VCL将会从主线程环境中创建和维护一个隐含的线程310第二部分高级技术 CM忖刚随-1下载I窗口。此窗口唯一的目的是把通过Synchronize。调用的方法排队。Synchronize()把由Method参数传递过来的方法保存在TThread的FMethod字段中,然后,给线程窗 口发一个CM_EXECPROC消息,并且把消息的IParam参数设为self(这里指线程对象)。当线程窗口的窗 口过程收到这个消息后,它就调用FMethod字段所指定的方法。由于线程窗口是在主线程内创建的,线程窗口的窗口过程也将被主线程执行。因此,FMethod字段所指定的方法就在主线程内执行。图11-2形象地说明了 Synchronize()的内部机制。Synchronize(F oo):主线程隐藏“线程窗口.线程窗口的窗口过程CK-EXECPROC 处理消息 o IParam 一强制转换为TThread,并调用FMethod。设置FMethod为Foo。发 送 CM_EXECPROC 消息 到线程窗口,在IParam 中传递Self。图 11-2 Synchronize()的原理3.用消息来同步可以利用在线程之间使用消息同步以替代TThread.Synchronize。方法。可以使用API函数 SendMessage()或PostMessage。来发送消息。例如,下面是一段用来在一个线程中设置另一个线程中 的编辑框文本的代码:varS:string;beginS:=hello from threadland;SendMessage(SomeEdit.Handle,WM_SETTEXT,0t Integer(PChar(S);end;11.2.5 一个演示程序为了充分地说明在Delphi中的多线程编程,下面介绍一个演示程序。可以把它存为EZThrd。这个 演示程序上有一个文本编辑器、一个按钮、一个编辑框和若干个标签,如图11-3所示。图11-3 EZThrd程序的主窗体此程序的主窗体单元代码在清单11-2中。清单11-2 MAIN.PAS单元 unit Main;interface第”章编写多线程应用程序usesWindows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls,ThrdU;typeTMainForm=class(TForm)Editl:TEdit;Buttonl:TButton;Memol:TMenio;Labell:TLabel;Label2:TLabel;procedure ButtonlClick(Sender:TObject);private Private declarations)public Public declarations end;var MainForni:TMainForm;implementation Int64(KernelTime)then Beep;为了帮助你学会TFileTime的用法,下面的代码将演示如何把TFileTime和TDateTime相互转换:function FileTimeToDateTime(FileTime:TFileTime):TDateTime;varSysTime:TSystemTime;beginif not FileTimeToSystemTime(FileTime,SysTime)thenraise EConvertError.CreateFmt(FileTimeToSystemTime failed.+,Error code%d*,GetLastError);with SysTime doResult:=EncodeDate(wYear,wMonth,wDay)+EncodeTime(wHourj wMinute,wSecond,wMilliseconds)end;function DateTimeToFileTime(DateTime:TDateTime):TFileTime;varSysTime:TSystemTime;beginwith SysTime do beginDecodeDate(DateTime y wYear,wMonth,wOay);DecodeTime(DateTime,wHour,wMinute,wSecond,wMilliseconds);wDayOfWeek:=DayOfWeek(DateTime);end;if not SystemTimeToFileTime(SysTime,Result)then raise EConvertError.CreateFmt(SystemTimeToFileTime failed.*+Error code,GetLastError);end;注意 请记住函数GetThreadTimes。只适用于Windows NT/2000。如果你在Windows 95/98下调 用,它总是返回False。非常不幸,Windows 95/98没有提供获取线程时间的手段。11.3管理多线程正如前面介绍的,尽管线程能够解决许多的问题,但同时它又给我们带来了许多的问题。其中主 要的问题是:对全局变量或句柄这样的全局资源如何访问?另外,当必须确保一个线程中的某些事件 要在另一个线程中的其他事件之前(或之后)发生时,该怎么办?在本节里你将学习通过使用由Delphi提 供的线程局部存储和API为线程提供同步的方法。11.3.1 线程局部存储由于每个线程都代表了一个不同的执行路径,因此,最好有一种只限于一个线程内部使用的数据第11章编写多线程应用程序 315存储方式。要实现上述目的有3种方式:第一种也是最简单的一种方式是局部变量(基于栈)。由于每个 线程都在各自的栈中,各个线程将都有一套局部变量的副本,这样,就不会相互影响。第二种方式是 把有关信息保存在各自的线程对象中。第三种方式是用Object Pascal的关键字threadvar来声明变量,以 利用操作系统级的线程局部存储。1.把信息保存在TThread派生对象中将相关信息保存在TThread派生对象中,这是一种线程局部存储可选用的技术。相对于使用关键 字threadvar的技术,这种方式更加简单、更加有效。例如,你可以在一个线程对象中加入下列信息:typeTMyThread=class(TThread)privateFLocallnt:Integer;FLocalStr:String;end;提示 由于访问线程对象中的数据比访问线程局部变量要快10倍,因此,你应当尽可能地把线 程专用的信息保存在线程对象中。对于那些只在过程或函数的生存期有意义的变量,应当把它 们声明为局部变量。2.threadvar:API线程局部存储在前面我们了解到:虽然对于局部变量,在每个线程中都一个副本,然而应用程序的全局变量是 被所有线程所共享的。例如,假设现在有一个用于设置和显示一个全局变量的值的过程。如果传递一 个字符串给它,就设置这个全局变量;如果传递一个空字符串给它,就显示这个全局变量的值。这个 过程定义如下:varGlobalStr:String;procedure SetShowStr(const S:String);beginif S=thenMessageBox(0,PChar(GlobalStr),The string is.1,MBOK)elseGlobalStr:=S;end;如果在某段代码中调用这个过程,则有可能出现一些问题。因为在调用它时,可以先设置而后显 示。可问题是,有可能有两个或更多的线程存在。当一个线程调用此过程来设置字符串时,而另一个 线程也可能调用了此过程来设置GlobalStr变量的值。当操作系统把CPU时间又分配给前一个线程时,该线程所设置的值有可能已经令人失望地丢失。为解决此类问题,Win32提供了一种称为线程局部存储的方式,它能使你在第一个运行的线程中 创建一个全局变量的拷贝。Delphi利用关键字threadvarif装此功能。在threadvar关键字下你可以声明任 何局部存储的变量。下面是全局变量GlobalStr的声明:threadvarGlobalStr:String;在清单11-3中演示了线程局部存储的用法。在程序的主窗体上有一个按钮,单击此按钮,就设置 并显示GlobalStr变量的值。然后再创建一个线程,GlobalStr变量的值又被设置并显示。在创建了一个 线程后,从主线程再次设置并显示GlobalStr变量的值。先后用var和threadvar来声明GlobalStr变量并运行此程序。你会在输出中看到不同。316第二部分高级技术清单 11-3 MAIN.PASDone,-sunit Main;interfaceusesWindows,Messagesj SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls;typeTMainForm=class(TForm)Buttonl:TButton;procedure ButtonlClick(Sender:TObject);private Private declarations)public Public declarations end;varMainForm:TMainForm;implementation$R*.DFM NOTE:Change GlobalStr from var to threadvar to see difference var/threadvarGlobalStr:string;typeTTLSThread=class(TThread)privateFNewStr:String;protectedprocedure Execute;override;publicconstructor Create(const ANewStr:String);end;procedure SetShowStr(const S:String);begin if S=*thenMessageBox(0,PChar(GlobalStr),*The string is.,MB_OK)elseGlobalStr S;end;constructor TTLSThread.Create(const ANewStr:String);beginFNewStr:=ANewStr;inherited Create(False);end;procedure TTLSThread.Execute;beginFreeOnTerminate:=True;SetShowStr(FNewStr);SetShowStr();end;procedure TMainForm.ButtonlClick(Sender:TObject);beginSetShowStr(Hello world);SetShowStr();TTLSThread.Create(Dilbert);Sleep(100);SetShowStr(*end;end.注意 演示程序中,在创建了线程之后,调用了一个Win32 Api过程Sleep。此过程声明如下:procedure Sleep(dwMilliseconds:DWORD);stdcall;Sleep。过程用来告诉操作系统,当前的线程在参数dwMilliseconds指定的时间内不需要分 配任何CPU时间。插入这个调用是使很多的任务在发生时,使执行哪个线程有一些随机性。通常,可以把参数dwMilliseconds设为0。尽管,这并没有使当前的线程真的“睡眠”,但它使操作系统把CPU时间分给了其他优先级相等或更高的线程。要小心Sleep。神秘的时间调整问题。Sleep。可能会使你的机器出现特别的问题。这种问题在另一台机器上可能无法再现。11.3.2线程同步当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程 序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文 件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把 两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解 决此问题,你必须使两个线程同步工作。存在一些线程同步地址的问题,Win32提供了许多线程同步的方式。在本节你将看到使用临界区、互斥、信号量和事件来解决线程同步的问题。为了检验这些技术,首先来看一个需要用线程同步来解决的问题。假设有一个整数数组,需要按 升序赋初值。现在要在第一遍把这个数组赋初值为1至128,第二遍将此数组赋初值为128至255,然后 结果显示在列表框中。要用两个线程来分别进行初始化。清单11-4列出了程序代码。清单11-4在两个线程中对数组进行初始化unit Main;interfaceusesWindows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls;typeTMainForm=class(TForm)Buttonl:TButton;318第二部分高级技术 CM忖刚也ListBoxI:TListBox;procedure ButtonIClick(Sender:TObject);privateprocedure ThreadsDone(Sender:TObject);end;TFooThread=class(TThread)protected procedure Execute;override;end;var MainForm:TMainForm;implementation$R*.DFMconst MaxSize=128;var NextNumber:Integer=0;DoneFlags:Integer=0;GlobalArray:array1.MaxSize of Integer;function GetNextNumber:Integer;begin Result:=NextNumber;/return global var Inc(NextNumber);/inc global varend;procedure TFooThread.Execute;var i:Integer;begin OnTerminate:=MainForm.ThreadsDone;for i:=1 to MaxSize do begin GlobalArrayi:=GetNextNumber;/set array element Sleep(5);/let thread intertwineend;end;procedure TMainForm.ThreadsDone(Sender:TObject);var i:Integer;begin Inc(DoneFlags);if DoneFlags=2 then/make sure both threads finished for i:=1 to MaxSize do 回 回图11-4线程没有 同步的输出至于 TRTLCriticalSection-1467B10”MB15l18l19BaBal26B28al32箝136工1end.因为两个线程同时运行,同一个数组在两个线程中被初始化会出现什 么呢?你可以从图11-4中看到结果。这个问题的解决方案是:当两个线程访问这个全局数组时,为防止它 们同时执行,需要使用线程的同步。这样,你就会得到一组合理的数值。1.临界区临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由 一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另 一个线程在第一个线程处理完之前是不会被执行的。在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。其声明如下:procedure InitializeCriticalSection(var IpCriticalSection:TRTLCriticalSection);stdcall;IpCriticalSection参数是一个TRTLCriticalSection类型的记录,并且是变参。是如何定义的,这并不重要,因为很少需要查看这个记录中的具体内容。只需要在IpCriticalSection中传 递未初始化的记录,InitializeCriticalSection()过程就会填充这个记录。注意 Microsoft故意隐瞒了TRTLCriticalSection的细节。因为,其内容在不同的硬件平台上是 不同的。在基于Intel的平台上,TRTLCriticalSection包含一个计数器、一个指示当前线程句柄 的域和一个系统事件的句柄。在Alpha平台上,计数器被替换为一种Alpha-CPU数据结构,称 为 spinlock。在记录被填充后,我们就可以开始创建临界区了。这时我们需要用 EnterCriticalSection()WLeaveCriticalSection()来封装代码块。这两个过程的声明如下:procedure EnterCriticalSection(var IpCriticalSection:TRTLCriticalSection);stdca
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服