delphi多线程(转) delphi多线程编程

   2023-02-09 学习力0
核心提示:2009-10-29 23:081-1 多线程的基本概念多线程带来如下好处:(自己阅读)1)避免瓶颈;2)并行操作;3)提高效率;在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU
2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

delphi多线程
2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

TThread是一个抽象类,可以创建几个独立的线程。
类关系 TObject
在一个多线程的应用程序中创建一个TThread的后子类代表一个线程。每一新子类的TThread对象的实例是一个新的线程。从TThread派生的多线程实例可以构成Delphi的多线程应用程序。
   当一个应用程序运行时,应用程序就被载入内存准备执行。此时,它成为包含一个或多个线程的进程,每个线程含有数据、代码和系统资源。线程执行应用程序的部分内容,并由系统分配CPU时间。同一进程的所有线程共享同一地址空间,可以访问进程的全局变量。线程通过以下工作改善应用的性能:管理多通信设备的输入。
   区分任务的优先级。优先级高的处理紧急的任务。优先级低的处理其他任务。
   以下是使用线程的一些建议:
   同时跟踪太多的线程消耗CPU时间。对单处理器系统,一个进程最多有16个线程。
   当多个线程更新相同的资源时,应使线程同步以避免冲突。
   大多数访问VCL对象和更新窗体的方法必须从主VCL线程内部调用。
   以下为创建和使用一个新线程的过程:
   (1)单击File|New|Thread菜单项,创建一个包含对象的新单元,该对象源于TThread类。
   (2)定义新线程对象和Create方法。
   (3)通过插入线程执行时需要的代码定义线程对象和Execute方法。
   (4)将使用VCL组件的任何调用传递给Synchronize方法,以避免多线程冲突。

属性列表
FreeOnTerminate 线程终止时该对象是否自动删除
Handle 包含线程句柄
Priority 确定该线程相对于进程中其他线程的优先级
ReturnValue 返回线程值
Suspended 指示一线程是否被挂起
Terminated 表明线程被要求终止
ThreadID 标识贯穿系统的线程

方法列表
~TThread 删除线程对象并释放其战用的内存空间
DoTerminate 产生一个OnTerminate事件
Execute 提供包含线程执行时所需代码的抽象方法
Resume 重新执行一个挂起的线程
Suspend 挂起一个运行中的线程
Synchronize 在主VCL线程中执行Method
Terminate 将Ternimated属性设置为True通知线程终止
TThread 创建一个线程对象的实例
WaitFor 等待线程终止并返回ReturnValue属性值

事件列表
OnTerminateExecute 方法已返回且该线程被删除前发生

属性

TThread::FreeOnTerminate
__property bool FreeOnTerminate = {read=FFreeOnTerminate,write=FFreeOnTerminate,nodefault};
确定当线程终止时,该线程对象是否自动删除。
FreeOnTerminate默认值为False,线程对象必须在代码中显示删除。
包含线程句柄。
当调用Win32API函数处理线程时,使用Handle.

TThread::Priority
__property TThreadPriority Priority = {read=GetPriority,write=SetPriority,nodefault};
确定该线程相对于进程中其他线程的优先级。
Priority属性为一枚举类型,其默认为tpNormal.
TThreadPriority类型定义了TThread组件的Priority属性的可能值,如下表所述。Windows根据优先级确定每一个线程的CPU周期。
_____________________________________________________________________
    值           含义
_____________________________________________________________________
tpIdle 只有当系统空闲时该线程执行
tpLowest 线程优先级比正常低2点
tpLower 线程优先级比正常低1点
tpNormal 线程优先级为正常值
tpHigher 线程优先级比正常高1点
tpHighest 线程优先级比正常高2点
tpTimeCritical 线程优先级最高

TThread::ReturnValue
__property int ReturnValue = {read=FReturnValue,write=FReturnValue,nodefault};
返回线程值。
使用ReturnValue应用为其他线程指示其成功/失败或数字结果/输出。WaitFor方法返回存储在ReturnValue中的值。

TThread::Suspended
__property bool Suspended = {read=FSuspended,write=SetSuspended,nodefault};
指示一线程是否被挂起。
除非重新执行,否则被挂起的线程不会继续执行。若将Suspended设置为True将挂起一个线程;若设置为False,则继续执行该线程。

TThread::Terminated
__property bool Terminated = {read=FTerminated,nodefault};
表明线程被要求终止。Terminate方法将Terminated属性设置为True。
线程的Execute方法和任何Execute调用的方法将周期性地检查Terminated,当其为True时,将终止执行。

TThread::ThreadID
__property int ThreadID = {read=FhreadID,nodefault};
标识贯穿系统的线程。
当调用Win32API函数处理线程时,ThreadID将十分有用。
注意:ThreadID与Handle属性不同。

方法

TThread::~TThread
__fastcall virvual ~TThread(void);
删除线程对象并释放其战胜的内存空间。
在应用中不要调用~TThread。用delete替代。
~TThread通知线程终止,并在调用Destroy方法前等待该线程返回。

TThread::DoTerminate
virtual void __fastcall DoTerminate(void);
产生一个OnTerminate事件。
DoTerminate调用OnTerminate时间句柄,但并不终止该线程。

TThread::Execute
virtual void __fastcall Execute(void) =0;
提供包含线程执行时所需代码的抽象方法。
Execute查看Terminated属性值以决定该线程是否需要终止。
当CreateSuspended被设置为False,当调用Create时,一线程执行;在线程创建后先调用了Resume且CreateSuspended为True,一线程执行。
注意:不要在线程的Execute方法中直接调用

其他对象的属性和方法。应该将对其他对象的使用分成几个不同的过程,将其作为一个传递到Synchronize方法的参数分别调用。

TThread::Resume
void __fastcall Resume(void);
重新执行一个挂起的线程。
调用Suspend可以嵌套。因此调用Resume必须注意次序。

TThread::Suspend
void __fastcall Suspend(void);
挂起一个运行中的线程。
调用Resume可以继续运行。调用Suspend可以嵌套。因此调用Resume必须次序。

TThread::Synchronize
typedef void __fastcall(__closure* TThreadMethod)(void);
void __fastcall Synchronize (TThreadMethod&Method);
在主VCL线程中执行Method。
Synchronize方法由Method指定的调用被主VCL线程执行。
注意:当在主VCL线程中执行Method时,当前的线程被挂起。

TThread::Terminate
void __fastcall Terminate(void);
通过将Terminated属性设置为True通知线程终止。
线程的Execute方法以及Execute调用的任何方法应周期性的检查Terminated,当其为True时终止运行。

TThread::TThread
__fastcall TThread(bool CreateSuspended);
创建一个线程对象的实例。
在应用中不要直接使用TThread来创建线程。用new替代,传递CreateSuspended参数argument。若CreateSuspended为False,Execute被立即调用。若CreateSuspended为True,Execute直到Resume被调用后才被调用。

TThread::WaitFor
int __fastcall WaitFor(void);
等待线程终止并返回ReturnValue属性值。
直到线程终止时WaitFor才返回,因此线程一定是因为结束了Execute方法或因Terminated属性为了True而终止。如果该线程使用Synchronize,则不要在主VCL线程中调用WaitFor,否则或者引起系统死机,或者产生一个EThread异常。
Synchronize在允许该方法生效前等待主VCL线程进入信息回路。若主VCL线程已经调用了WaitFor,它将不会进入信息回路,Synchronize也永远不会返回。此时,TThread将产生一个EThread意外并使该线程终止;而且如果该意外没有被Execute方法截获,该应用也将终止。如果调用WaitFor时,Synchronize已经在VCL线程中等待,TThread将不会干预,应用程序将死机。

事件

TThread::OnTerminate
__property TNotifyEvent OnTerminate = {read=FOnTerminate,write=FOnTerminate};
当线程的Execute方法已经返回且在该线程被删除之前发生。
OnTerminate事件句柄在主VCL线程被调用。该线程对象也可在该事件中被释放。

2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

delphi多线程
2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

2009-10-29 23:08

1-1 多线程的基本概念

多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。
注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

1-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),可以采用两种方法:

1)在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上
Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过多,所以,一般要用什么拷什么。

三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread 对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。只需要简单的调用该线程的Terminate方法,并设直线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread 对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


1-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控件联系就可以了,这样就可以实现前后台处理数据库问题。

二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session的问题。在单线程时,每个数据源的建立就自动生成一个Session,这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比较广泛。

TThread是一个抽象类,可以创建几个独立的线程。
类关系 TObject
在一个多线程的应用程序中创建一个TThread的后子类代表一个线程。每一新子类的TThread对象的实例是一个新的线程。从TThread派生的多线程实例可以构成Delphi的多线程应用程序。
   当一个应用程序运行时,应用程序就被载入内存准备执行。此时,它成为包含一个或多个线程的进程,每个线程含有数据、代码和系统资源。线程执行应用程序的部分内容,并由系统分配CPU时间。同一进程的所有线程共享同一地址空间,可以访问进程的全局变量。线程通过以下工作改善应用的性能:管理多通信设备的输入。
   区分任务的优先级。优先级高的处理紧急的任务。优先级低的处理其他任务。
   以下是使用线程的一些建议:
   同时跟踪太多的线程消耗CPU时间。对单处理器系统,一个进程最多有16个线程。
   当多个线程更新相同的资源时,应使线程同步以避免冲突。
   大多数访问VCL对象和更新窗体的方法必须从主VCL线程内部调用。
   以下为创建和使用一个新线程的过程:
   (1)单击File|New|Thread菜单项,创建一个包含对象的新单元,该对象源于TThread类。
   (2)定义新线程对象和Create方法。
   (3)通过插入线程执行时需要的代码定义线程对象和Execute方法。
   (4)将使用VCL组件的任何调用传递给Synchronize方法,以避免多线程冲突。

属性列表
FreeOnTerminate 线程终止时该对象是否自动删除
Handle 包含线程句柄
Priority 确定该线程相对于进程中其他线程的优先级
ReturnValue 返回线程值
Suspended 指示一线程是否被挂起
Terminated 表明线程被要求终止
ThreadID 标识贯穿系统的线程

方法列表
~TThread 删除线程对象并释放其战用的内存空间
DoTerminate 产生一个OnTerminate事件
Execute 提供包含线程执行时所需代码的抽象方法
Resume 重新执行一个挂起的线程
Suspend 挂起一个运行中的线程
Synchronize 在主VCL线程中执行Method
Terminate 将Ternimated属性设置为True通知线程终止
TThread 创建一个线程对象的实例
WaitFor 等待线程终止并返回ReturnValue属性值

事件列表
OnTerminateExecute 方法已返回且该线程被删除前发生

属性

TThread::FreeOnTerminate
__property bool FreeOnTerminate = {read=FFreeOnTerminate,write=FFreeOnTerminate,nodefault};
确定当线程终止时,该线程对象是否自动删除。
FreeOnTerminate默认值为False,线程对象必须在代码中显示删除。
包含线程句柄。
当调用Win32API函数处理线程时,使用Handle.

TThread::Priority
__property TThreadPriority Priority = {read=GetPriority,write=SetPriority,nodefault};
确定该线程相对于进程中其他线程的优先级。
Priority属性为一枚举类型,其默认为tpNormal.
TThreadPriority类型定义了TThread组件的Priority属性的可能值,如下表所述。Windows根据优先级确定每一个线程的CPU周期。
_____________________________________________________________________
    值           含义
_____________________________________________________________________
tpIdle 只有当系统空闲时该线程执行
tpLowest 线程优先级比正常低2点
tpLower 线程优先级比正常低1点
tpNormal 线程优先级为正常值
tpHigher 线程优先级比正常高1点
tpHighest 线程优先级比正常高2点
tpTimeCritical 线程优先级最高

TThread::ReturnValue
__property int ReturnValue = {read=FReturnValue,write=FReturnValue,nodefault};
返回线程值。
使用ReturnValue应用为其他线程指示其成功/失败或数字结果/输出。WaitFor方法返回存储在ReturnValue中的值。

TThread::Suspended
__property bool Suspended = {read=FSuspended,write=SetSuspended,nodefault};
指示一线程是否被挂起。
除非重新执行,否则被挂起的线程不会继续执行。若将Suspended设置为True将挂起一个线程;若设置为False,则继续执行该线程。

TThread::Terminated
__property bool Terminated = {read=FTerminated,nodefault};
表明线程被要求终止。Terminate方法将Terminated属性设置为True。
线程的Execute方法和任何Execute调用的方法将周期性地检查Terminated,当其为True时,将终止执行。

TThread::ThreadID
__property int ThreadID = {read=FhreadID,nodefault};
标识贯穿系统的线程。
当调用Win32API函数处理线程时,ThreadID将十分有用。
注意:ThreadID与Handle属性不同。

方法

TThread::~TThread
__fastcall virvual ~TThread(void);
删除线程对象并释放其战胜的内存空间。
在应用中不要调用~TThread。用delete替代。
~TThread通知线程终止,并在调用Destroy方法前等待该线程返回。

TThread::DoTerminate
virtual void __fastcall DoTerminate(void);
产生一个OnTerminate事件。
DoTerminate调用OnTerminate时间句柄,但并不终止该线程。

TThread::Execute
virtual void __fastcall Execute(void) =0;
提供包含线程执行时所需代码的抽象方法。
Execute查看Terminated属性值以决定该线程是否需要终止。
当CreateSuspended被设置为False,当调用Create时,一线程执行;在线程创建后先调用了Resume且CreateSuspended为True,一线程执行。
注意:不要在线程的Execute方法中直接调用

其他对象的属性和方法。应该将对其他对象的使用分成几个不同的过程,将其作为一个传递到Synchronize方法的参数分别调用。

TThread::Resume
void __fastcall Resume(void);
重新执行一个挂起的线程。
调用Suspend可以嵌套。因此调用Resume必须注意次序。

TThread::Suspend
void __fastcall Suspend(void);
挂起一个运行中的线程。
调用Resume可以继续运行。调用Suspend可以嵌套。因此调用Resume必须次序。

TThread::Synchronize
typedef void __fastcall(__closure* TThreadMethod)(void);
void __fastcall Synchronize (TThreadMethod&Method);
在主VCL线程中执行Method。
Synchronize方法由Method指定的调用被主VCL线程执行。
注意:当在主VCL线程中执行Method时,当前的线程被挂起。

TThread::Terminate
void __fastcall Terminate(void);
通过将Terminated属性设置为True通知线程终止。
线程的Execute方法以及Execute调用的任何方法应周期性的检查Terminated,当其为True时终止运行。

TThread::TThread
__fastcall TThread(bool CreateSuspended);
创建一个线程对象的实例。
在应用中不要直接使用TThread来创建线程。用new替代,传递CreateSuspended参数argument。若CreateSuspended为False,Execute被立即调用。若CreateSuspended为True,Execute直到Resume被调用后才被调用。

TThread::WaitFor
int __fastcall WaitFor(void);
等待线程终止并返回ReturnValue属性值。
直到线程终止时WaitFor才返回,因此线程一定是因为结束了Execute方法或因Terminated属性为了True而终止。如果该线程使用Synchronize,则不要在主VCL线程中调用WaitFor,否则或者引起系统死机,或者产生一个EThread异常。
Synchronize在允许该方法生效前等待主VCL线程进入信息回路。若主VCL线程已经调用了WaitFor,它将不会进入信息回路,Synchronize也永远不会返回。此时,TThread将产生一个EThread意外并使该线程终止;而且如果该意外没有被Execute方法截获,该应用也将终止。如果调用WaitFor时,Synchronize已经在VCL线程中等待,TThread将不会干预,应用程序将死机。

事件

TThread::OnTerminate
__property TNotifyEvent OnTerminate = {read=FOnTerminate,write=FOnTerminate};
当线程的Execute方法已经返回且在该线程被删除之前发生。
OnTerminate事件句柄在主VCL线程被调用。该线程对象也可在该事件中被释放。

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Delphi中的消息处理机制 delphi 方法
    每一个VCL都有一内在的消息处理机制,其基本点就是构件类接收到某些消息并把它们发送给适当的处理方法,如果没有特定的处理方法,则调用缺省的消息处理句柄。    其中mainwndproc是定义在Twincontrol类中的一个静态方法,不能被重载(Override)。它不直接处
    02-09
  • Delphi XE6 通过JavaScript API调用百度地图
    Delphi XE6 通过JavaScript API调用百度地图
    参考昨天的内容,有朋友还是问如何调用百度地图,也是,谁让咱都在国内呢,没办法,你懂的。 首先去申请个Key,然后看一下百度JavaScript的第一个例子:http://developer.baidu.com/map/jsdemo.htm下一步,就是把例子中的代码,移动TWebBrower中。 unit Unit
    02-09
  • Delphi编译/链接过程 delphi编程案例
    Delphi编译/链接过程 delphi编程案例
    下面展示了Delphi是怎样编译源文件,并且把它们链接起来,最终形成可执行文件。当Delphi编译项目(Project)时,将编译项目源文件、窗体单元和其他相关单元,在这个过程中将会发生好几件事情:首先,Object Pascal编译器把项目单元编译为二进制对象文件,然后
    02-09
  • Delphi CompilerVersion Constant / Compiler C
    http://delphi.wikia.com/wiki/CompilerVersion_Constant The CompilerVersion constant identifies the internal version number of the Delphi compiler.It is defined in the System unit and may be referenced either in code just as any other consta
    02-09
  • Delphi revelations #1 – kbmMW Smart client
    Delphi 启示 #1 – kbmMW Smart client on NextGen (Android) – 作用域问题以更高级的方式使用kbmMW smart client,在Android设备上,我遇到了问题。通过继承TInvokeableVariantType,kbmMW smart client可以使用Delphi支持的特殊类型的自定义Variant,从而可
    02-09
  • Delphi 调用DLL外部函数时的指针参数
    某项目需要调用设备厂家提供的DLL的函数,厂家给了一个VB的例子,有个参数是ByRef pBuffer As Single。于是在Delphi中用buffer:array of single代替:function func(buffer:array of single;count:integer):integer;far;stdcall;external 'func.dll';调用后bu
    02-09
  • 《zw版·Halcon-delphi系列原创教程》 Halcon分
    《zw版·Halcon-delphi系列原创教程》 Halcon分类函数012,polygon,多边形为方便阅读,在不影响说明的前提下,笔者对函数进行了简化::: 用符号“**”,替换:“procedure”:: 用大写字母“X”,替换:“IHUntypedObjectX”:: 省略了字符:“const”、“OleVa
    02-09
  • 最简单的delphi启动画面(转)
    首先做一窗体,然后将BorderStyle的属性设为bsnone,放image控件,align设为alclient 然后将主程序的修改为 uses Windows, Forms, Unit1 in 'Unit1.pas' {Form1}, Unit2 in 'Unit2.pas' {Form2}; {$ R *.res} begin Application.Initialize; Form2:=TForm2.Cre
    02-09
  • Delphi备忘三:TCollection的使用,用Stream保
     代码unit ufrmGetFunctionDefine;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,TypInfo,  Dialogs,ufrmStockBaseCalc, StdCtrls, ComCtrls,uQEFuncManager,uWnDataSet,uDataService;type  T
    02-09
  • Delphi Dcp 和BPL的解释
    dcp = delphi compiled package,是 package 编译时跟 bpl 一起产生出来的,记录着 package 中公开的 class、procedure、function、variable、const.... 等等的名称和相对位址。package英文翻译过来就是“包”。如果 某个控件包 A 引用了 控件包 B,当 控件包
    02-09
点击排行