[转]Delphi多线程编程入门(一)

时间:2021-07-14 10:59:49

最近Ken在比较系统地学习Delphi多线程编程方面的知识,在网络上查阅了很多资料。现在Ken将对这些资料进行整理和修改,以便收藏和分享。内容基本上是复制粘贴,拼拼凑凑,再加上一些修改而来。各个素材的来源已经很难搞清楚,因此不再一一说明。一些资料可能有点老,但仍然有参考价值。篇幅比较长,耐心点看完吧。

多线程共存于应用程序中是现代操作系统中的基本特征和重要标志。为了提高程序的运行效率,在操作系统中提出了进程和线程的概念,在一个进程中可以包含多个线程,进程作为资源分配的基本单位,线程作为独立运行和独立调度的基本单位。关于多线程的更详细说明建议粗略地阅读下百度百科《多线程》。现在PC的硬件性能越来越好,多线程能够充分发挥这些性能,让程序的用户体验更好。

多线程的两个概念:

1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。

注意:进程本身并不一定要正在执行。进程由以下几部分组成:

a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;

b>程序的相关代码、数据源;

c>系统资源,比如操作系统同步对象等;

d>至少包含一个线程(主线程);

2)线程:是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。

线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,线程主要由如下两部分组成:

a>数据结构;

b>CPU 寄存器和堆栈;

一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

多线程带来如下好处:

1)避免瓶颈;

2)并行操作;

3)提高效率;

在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性。

另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的多CPU同时操作)。

也有潜在的坏处:

1)写得不好更容易出现未知错误;

2)如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换;

3)更多的线程需要更多的内存空间;

4)线程的中止需要考虑其对程序运行的影响。

当然只要熟悉多线程编程,这些坏处都可以尽量避免的。

下面提供一些代码,供大家在尝试中去理解。

procedure TForm1.Button1Click(Sender: TObject); 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(10, 10, IntToStr(i)); 
  end; 
end;

上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试...

Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:

procedure TForm1.Button1Click(Sender: TObject); 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(10, 10, IntToStr(i)); 
    Application.ProcessMessages; 
  end; 
end;

这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.

但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来...

在使用多线程以前, 让我们先简单修改一下程序:

function MyFun: Integer; 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end; 
  Result := 0; 
end; 
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  MyFun; 
end;

细数上面程序的变化:

1、首先这还不是多线程的, 也会让窗体假 "死" 一会;

2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);

3、既然是个函数, (不管是否必要)都应该有返回值;

4、使用了 500001 次 Lock 和 Unlock.

Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;

Canvas.Unlock : 用完了, 解锁!

在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.

在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单,但Ken建议用 TThread 类. 以下是使用 API 的代码:

function MyFun(p: Pointer): Integer; stdcall; 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end; 
  Result := 0; 
end; 
 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: THandle; 
begin 
  CreateThread(nil, 0, @MyFun, nil, 0, ID); 
end;

代码分析:

CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了;

CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.

CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;

因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.

CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.

这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次

type 
  TMyThread = class(TThread) 
  protected 
    procedure Execute; override; 
  end; 
 
procedure TMyThread.Execute; 
var 
  i: Integer; 
begin 
  FreeOnTerminate := True; {这可以让线程执行完毕后随即释放} 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(10, 10, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end; 
end; 
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  TMyThread.Create(False); 
end;

TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为 TMyThread.

继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.

按常规我们一般这样:

procedure TForm1.Button1Click(Sender: TObject); 
var 
  MyThread: TMyThread; 
begin 
  MyThread := TMyThread.Create(False); 
end;

因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做 TMyThread.Create(False);

我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?

这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程, 譬如:

procedure TForm1.Button1Click(Sender: TObject); 
var 
  MyThread: TMyThread; 
begin 
  MyThread := TMyThread.Create(True); 
  MyThread.Resume; 
end; 
 
//可简化为: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  with TMyThread.Create(True) do Resume; 
end;

想进一步了解Delphi多线程编程,请继续看下一篇博文。