打印本文 打印本文 关闭窗口 关闭窗口
Delphi线程类的使用(1)
作者:武汉SEO闵涛  文章来源:敏韬网  点击数2308  更新时间:2009/4/23 18:29:27  文章录入:mintao  责任编辑:mintao

Delphi线程类的使用

 

猛禽[Mental Studio](个人专栏)(BLOG)

http://mental.mentsu.com

 

去年底我写过一篇文章《Delphi中的线程类》(在本文中称之为“前文”),剖析了TThread类的实现细节,分析了使用TThread类时需要注意的一些问题。估计那篇文章还是太过于理论化,没有结合实际应用,所以再写了这篇文章来做一个补充。

 

派生线程类

在多线程应用中访问可视控件

线程间数据共享

线程间通信

防止死锁

正常的线程结束方式

强制结束线程

应用实例:多线程数据库应用

应用实例:多线程网络应用

应用实例:线程池技术

 

派生线程类

在开始介绍线程的使用前,首先要说明一下派生线程类时需要注意的一些方面。

一般来说,派生线程类的方法都是类似于下面这样的:

 

  TDemo1Thread = class(TThread)

  private

    { Private declarations }

    //  这里定义线程类中所用到的一些数据

    procedure SyncProc;  //  用于被Synchronize调用的方法

  protected

procedure Execute; override;

  public

       constructor Create;  //  构造函数,初始化

  end;

 

在前文中已经说过,线程、线程类、线程类对象是不同的东西,如下表:

 

线程

线程是一段可以并发执行的代码,在DELPHI线程类中就是Execute方法的实现代码,包括此方法实现中被直接或间接调用的所有代码。

线程类

一个对线程进行封装的类,它包括线程方法Execute,线程数据(注意,它的数据成员并非线程的局部数据,对它们的访问要考虑访问冲突问题,关于这点将在后面详述),及其它方法(虽然是线程类方法,但大多数实际上是在主线程中运行,见后面说明)

线程类对象

即线程类的实例化,与一般类的实例化基本一样,都是在堆中分配相应的数据区,用于记录线程数据,因为它与一般类对象一样,都是分配在进程的堆空间中,并非线程的局部数据,所以要考虑数据访问冲突问题

 

在这里有两个东西必须明确:一个是线程是一个动态的概念;另一个是类的实例化细节。

所谓线程是动态的概念就是说:必须在执行的情况下讨论线程代码才是有意义的,在静止状态下,这是没有意义的。比如说,一般情况下,Execute方法中的代码我们可以说是线程代码,那是因为在正常情况下,它是被实际的线程代码ThreadProc(见前文)所调用的,是在线程中被执行的。但是这只是通常情况,举一个极端的例子:如果把线程类的Execute放到public里,然后在主线程中调用Execute,这时就不能说Execute是线程代码,因为它是被主线程调用,本质上来说,它就是主线程代码。

反过来说,所有被线程方法(如正常情况下的Execute)直接或间接(但不是通过Synchronize或类似的方法)调用到的代码都是线程代码,如在Execute中调用线程类以外的代码。这就是为什么在线程中访问VCL组件必须通过Synchronize,因为否则的话就是多线程访问,可能导致数据访问冲突。但通过Synchronize,可以将子线程要执行代码通过消息传递给主线程,由主线程来执行,这样的话,这段代码就不是子线程代码,而是一般的主线程代码,所以就没有问题了。

举个说明问题的例子:

  TDemo1Thread = class(TThread)

  private

    FData : Integer;

  protected

    procedure Execute; override;

  public

    procedure Foo;

  end;

 

procedure TDemo1Thread.Execute;

begin

  Foo;

  Form1.Caption := ‘Changed by thread’;  //  错误:使用了不是线程安全的VCL操作

end;

 

procedure TDemo1Thread.Foo;

begin

    FData := FData + 1;

end;

 

//  在主线程中调用

procedure TForm1.Button1Click(Sender: TObject);

Var

    t : TDemo1Thread;

begin

    t := TDemo1Thread.Create( false );

    t.Foo;  //  错误:Foo中操作了数据成员FData,且Foo同时被Execute调用,存在访问冲突

    t.Free;

end;

其中的过程Foo就是同时被Execute和主线程调用,当它被Execute调用执行时就是(子)线程代码,当它被主线程调用执行时就不是。而在Execute里通过Form1的Caption属性修改窗体标题使TForm的SetCaption代码变成线程代码,因为VCL不是线程安全的,所以这种操作可能导致不可预料的后果。同时,由于在Foo中修改了数据成员FData,当子线程和主线程都可能调用Foo的情况下,可能导致数据访问冲突。关于访问冲突的具体分析见前文。

如下图是一个典型的线程类应用的执行情况。很明显,其中的线程类代码中,只有Execute方法是线程代码,因为它被真正的线程方法ThreadProc所调用,而线程类的构造器Create和受Synchronize保护的SyncProc都是主线程代码。但如果SyncProc是直接在Execute中被调用,而不是通过Synchronize,则它也会成为线程代码。

 

主窗体类代码

 

线程类.Create

其它代码…

 

主线程

线程类代码

 

Create

 

 

 

 

 

 

 

ThreadProc

子线程

[1] [2] [3]  下一页

打印本文 打印本文 关闭窗口 关闭窗口