Chinaunix首页 | 论坛 | 博客
  • 博客访问: 61178
  • 博文数量: 12
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 125
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-06 19:21
文章分类

全部博文(12)

文章存档

2008年(12)

我的朋友

分类:

2008-12-23 19:17:59

概述

   Delphi提供了好几个对象以方便进行多线程编程。多线程应用程序有以下三个方面的功能:

   避免性能瓶颈:单线程应用程序在进行比较慢的操作如磁盘读写时,CPU必须停下来等待,直到该操作执行完毕。而多线程应用程序则可以在一个线程进行磁盘读写等时,继续执行另一个单独的线程;

   组织应用程序的行为:通常,一个程序的行为可以组织成几个功能相互独立的平行的处理过程。将这些独立的处理组织成单独的几个线程,就可以同时启动这几个处理过程。还可以使用线程的优先级来控制哪些任务获得更多的CPU时间。

 

定义线程对象

   在大多数情况下,可以使用线程对象来代表一个线程。线程对象封装了常用的线程操作,从而简化了线程编程。

   注意:线程对象不允许控制线程的安全属性和栈的大小。如果你确实要控制这些东西,那只能够使用BeginThread函数来创建线程了。

   要在程序中使用线程对象,必须自定义一个从TThread派生的类。可以使用向导来帮助创建,生成的代码如下:

unit Unit2;

 

interface

uses

  Classes;

type

  TMyThread = class(TThread)

  private

    { Private declarations }

  protected

    procedure Execute; override;

  end;

implementation

{ TMyThread }

procedure TMyThread.Execute;

begin

  { Place thread code here }

end;

end.

在上面自动生成的代码中,可以:

  ·加入自己的线程初始化代码(可选)

  ·将线程执行的操作写入Execute函数

  ·加入自己的线程初始化代码(可选)

 

初始化线程

  如果你需要为你新创建的线程类加入初始化代码,就必须重写Create方法。你可以在Create方法里面给线程赋优先级,并可以决定线程在任务完成后是否自动销毁。

1.         给线程赋一个默认优先级

可以使用Priority属性来给线程赋优先级

Windows下,Priority可以取下面的值:

      优先级

tpIdle   The thread executes only when the system is idle. Windows won't interrupt other threads to execute a thread with tpIdle priority.

tpLowest      The thread's priority is two points below normal.

tpLower       The thread's priority is one point below normal.

tpNormal     The thread has normal priority.

tpHigher      The thread's priority is one point above normal.

tpHighest     The thread's priority is two points above normal.

tpTimeCritical    The thread gets highest priority.

   下面的代码演示了一个低优先级的线程的构造函数,该线程在后台执行一些任务:

             constructor TMyThread.Create(CreateSuspended: Boolean);

begin

  inherited Create(CreateSuspended);

  Priority := tpIdle;

end;

 

2.         指示线程是否自动销毁

通常,当一个线程完成了它的操作之后,它们就应当被销毁。如果是这样的话,最好的方式就是让线程自动销毁。要让一个线程自动销毁的话,可以在线程构造函数里将它的FreeOnTerminate属性置为True;

然而有些时候,一个线程的结束必须与其它线程同步。如你或许正在线程A中等待线程B的返回值,这个时候你肯定不想线程B在线程A收到返回值前就自动销毁。在这种情况下,就必须将线程BFreeOnTerminate属性置为False,然后在线程A接收到返回值后,显式销毁线程B

3.          

线程函数

TThreadExecute方法是线程函数。你可以将线程函数看作是一个由你的程序启动的独立的程序。只不过是它共享你的程序的进程空间。写线程函数比写一个单独的程序要复杂,因为你必须确保线程函数没有覆盖你的程序当中的其它线程所使用的内存区域。从另一方面讲,由于多个线程之间是共享同一个进程空间的,因此你可以在多个线程之间通过共享内存进行交流。

1.         使用主线程

当你使用VCL中的对象时,它们的属性和方法都不保证是线程安全的。即访问属性或执行方法时涉及到的一些操作或许会用到别的线程可能正在使用的未受保护的内存区域。考虑到这一情况,Delphi专门设置了一个主线程来访问VCL对象。主线程处理程序中所有组件接收到的Windows消息。

如果所有的对象都在同一个线程中访问它们的属性及执行它们的方法,那么你就不必担心这些对象之间相互影响。可是如果你想在主线程以外的线程中访问VCL对象,那怎么办呢?可以创建一个单独的方法并在这个方法中执行访问主线程VCL对象的操作,然后使用Synchronize方法来调用这个方法。如


procedure TMyThread.PushTheButton;

begin

  Button1.Click;

end;

procedure TMyThread.Execute;

begin

  ...

  Synchronize(PushTheButton);

  ...

end;

 

Synchronize会等待主线程进入消息循环时才执行传入的方法

当你确信某个对象的方法是线程安全的时,可以略去Synchronize,这样可以提高程序的效率,因为不必等待主线程进入消息循环。

   建议在主线程中周期性的调用CheckSynchronize,以便后台线程能够执行它们的Synchronize方法。当然调用CheckSynchronize方法的最佳时机应该是当主线程处于Idle(可以在OnIdle事件处理程序中调用CheckSynchronize)

2.         使用线程局部变量

线程函数以及在线程函数里面调用的任何其它的函数都可以定义它们自己的局部变量,也可以访问任何全局变量。事实上,全局变量是在多个线程之间进行交流的一个很有效的手段。

有些时候,你可以需要这样的一些变量:同一个线程对象里面的所有函数都可以访问,但是其它的线程对象里面的函数则不能访问。可以声明线程局部变量,线程局部变量在threadvar程序段进行声明。如:

threadvar

x : integer;

     threadvar程序段只能声明全局变量。指针和函数变量不能够成为线程变量。使用了

   copy-on-write技术的类型(long strings)也不能声明线程变量。

    

3.         Terminate

你可以允许其它的线程来向一个线程发出结束的信号。当其它的线程想要结束该线程时,可以调用它的Terminate方法。Terminate方法将Terminated属性置为True。如

procedure TMyThread.Execute;

begin

  while not Terminated do

    PerformSomeTask;

end;

4.         在线程函数里面处理异常

Execute函数必须捕获其中发生的所有异常。如果你在线程函数中漏掉了某个异常的话,那么应用程序就会出现非法访问的情况。

要捕获线程函数里面的所有异常,可以在Execute方法中使用try…except语句块。如:

 

 

 

procedure TMyThread.Execute;

begin

  try

    while not Terminated do

      PerformSomeTask;

  except

    { do something with exceptions }

  end;

end;

 

5.          

销毁线程

   在线程结束时,会触发OnTerminate事件。可以将clean-up代码放在OnTerminate事件的处理程序中。

OnTerminate事件处理程序不是做为线程的一部分来运行,而是运行在主线程的上下文中的。因此:

  ·你不能在OnTerminate事件处理程序中使用线程变量

  ·你可以在OnTerminate事件处理程序中安全的访问任何VCL对象

 

  

多线程并发执行

   当你写线程代码时,必须要考虑到其它的线程可能会与当前线程同时执行这一事实。尤其是需要避免多个线程同时访问相同的全局对象。此外,一个线程里面的代码可能会依赖其它线程的执行结果。

1.         避免同时访问

      在访问全局对象时为了避免和其它的线程发生冲突,需要阻塞其它线程的操作直到当前线程的访问代码执行完毕。需要注意不要无谓的阻塞其它线程的操作,阻塞操作会极大的降低程序的执行效率。无谓的阻塞操作有背使用线程的初衷。

      VCL支持三种方法来阻止其它线程访问当前线程正在访问的内存区域:锁定对象、使用关键区、使用multi-read exclusive-write synchronizer

     锁定对象

一些对象有内置的锁机制。比如,Canvas对象就有一个Lock方法,可以阻止其它线程的访问直到调用UnLock方法

     关键段

如果对象没有提供内置的锁机制,你可以使用关键段。关键段就像一个门,同一时间只允许一个线程进入。要使用关键段,先创建一个TCriticalSection类的全局对象。TCriticalSection有两个方法,Acquire(阻塞其它方法)Release(移除阻塞)

每个关键段与你要保护的全局内存区域相关。每个访问全局内存区域的线程都必须首先使用Acquire方法来保证没有别的线程在使用它。当访问结束后,线程调用Release方法以便其它的线程能够通过Acquire方法来访问该全局内存区域。

注意:关键段只有在所有的线程都使用它们时才起作用。如果有线程忽视关键段,不通过Acquire访问内存区域,则会导致并发问题。

      

2.         等待其它线程

如果当前线程需要等待另一个线程完成某个任务,你可以让当前线程暂停执行。

     等待另一个线程结束

可以使用WaitFor方法等待另一个线程结束。Waitfor直到另一个线程结束后才返回。如:

if ListFillingThread.WaitFor then

begin

  with ThreadList1.LockList do

  begin

    for I := 0 to Count - 1 do

      ProcessItem(Items[I]);

  end;

  ThreadList1.UnlockList;

end;

     等待一个任务结束

有时,需要等待另一个线程执行完某些操作之后而不是完全结束。为此,应当使用一个事件对象。事件对象(TEvent)应当在全局范围内被创建以便它们能够被所有的线程访问。

当一个线程完成了一个操作,并且该操作是其它线程依赖的,那么它就调用TEvent.SetEvent打开信号,于是所有的其它线程都可以通过检测知道该操作执行完毕了。要关闭信号,使用ResetEvent操作。

      

3.          

执行线程对象

   启动和停止线程:

   一个线程在它完成之前,可以被停止和启动任意次。临时停止一个线程,使用Suspend函数。调用Resume方法恢复线程。Suspend函数会增加内部计数器,而Resum则减少内部计数器,因此你可以嵌套的多次调用SuspendResume。调用Resume只有当线程计数器为0时,线程才会真正启动起来。

可以调用线程的Terminate方法来永久的终止线程。Terminate设置线程的Terminated属性为True.

 

调试多线程应用程序

   当调试多线程应用程序时,跟踪所有同时执行的线程的状态,甚至只是想知道在断点处哪一个线程还在执行都是一件很郁闷的事。可以使用Thread status Box来帮助跟踪和操纵应用程序中的所有线程。要显示Thread status Box,从主菜单中选择View|Threads

当一个调试事件(断点,异常,暂停等)发生时,Thread status Box将会指示各个线程的状态。右击Thread status Box来访问。。。。

Thread status Box按照线程ID列出了程序中的所有线程。如果你使用线程对象,那么线程ID是属性ThreadID的值。如果使用的不是线程对象,那么线程IDBeginThread函数的返回值。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

阅读(7868) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~