Delphi线程类的使用

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

http://mental.mentsu.com

去年底我写过一篇文章《Delphi中的线程类、2、3、4、5》(在本文中称之为“前文”),剖析了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,则它也会成为线程代码。

再来看类的实例化细节。线程类的实例化与一般类大体相同,但也有其不同之处,最大的不同就是,它在进行实例化的同时还创建了一个线程(即ThreadProc的执行)。

对于一般类的实例化来说:首先是调用类构造器(Constructor)Create,而Create的工作首先就是在进程的堆空间中分配类的数据区(包括类的数据成员和一些必要额外数据),然后把这个数据区的指针作为Self参数调用Create完成构造工作。此后,我们所用到的类实例,其实具体的就是这个数据区。比如我们要调用某个类实例(SomeObject)的某成员方法(SomeMemberMethod)如下:

SomeObject.SomeMemberMethod(Parameters);

其实就等效于:

TSomeClass.SomeMemberMethod(SomeObject, Parameters);

其中TSomeClass就是类实例SomeObject对应的类,而相应的,类实例SomeObject被作为隐含参数Self传递到方法内部。

而在方法内部访问类数据成员(SomeMemberData)时:

Function TSomeClass.SomeMemberMethod(Parameters) : SomeType; // 因为实际上Self是隐含传递的

Begin

Result := SomeMemberData;

End;

其实等效于Self.SomeMemberData。

对于线程类的情况,与上面说的基本相同。

但是有一个最大的不同就在于:线程类实例会创建自己独立的栈(由线程函数ThreadProc隐含创建),而普通类是使用主线程栈的。这就是意味着,在子线程中使用局部变量是安全的,因为局部变量是分配在栈中的。各个线程都有自己的栈(包括主线程),而且一般情况下是无法直接访问别的线程的栈空间的,除非是一些极端的情况(如将局部变量通过指针传给其它线程供操作),局部变量都不需要访问冲突保护。

但这不表示线程类数据成员(如前面的FData)安全,因为它们是分配在进程的堆空间中。当然,每个线程类对象都有各自独立的数据成员,正常情况下只要不互相访问,它们仍然是安全的。但是如果需要让别的线程,特别是主线程使用线程类数据成员,就一定要考虑到访问冲突保护的问题(如前面的例子,FData通过Foo函数导致了主线程对它的访问),因为这种冲突访问通常是通过线程类本身的方法/属性间接进行的,有时很容易被忽视。

实现后的线程类运行时情况如下图:

代码区  数据区

在派生线程类中,最后要说的是:经常检查Terminated属性。因为正常的线程结束方式是执行完线程代码返回(不是返回主线程,是返回操作系统)后即结束。所以,如果要在线程中执行长时间的操作又需要能随时中断,推荐的办法是将长时间的操作分成很多短的操作(时间限制为在交互操作中表现的延时在可以接受的范围内,如可以接受在按下取消按钮后一秒钟内取消操作,则短操作的时间不能长于一秒钟),然后用循环来执行这些短操作,并且在每次循环时检查Terminated属性,一旦检测到Terminated为true,就可以立即取消操作。通常用下面这样的代码来实现:

procedure TDemo1Thread.Execute;

begin

While ( Not Terminated ) Do

Begin

//  短操作

End;

end;

<未完待续>

Delphi线程类的使用(1)相关推荐

  1. DELPHI 线程类

    转自http://www.cnblogs.com/chengxin1982/archive/2009/10/04/1577879.html Delphi中有一个线程类TThread是用来实现多线程编程 ...

  2. Delphi中的线程类

    Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchr ...

  3. Delphi中线程类TThread实现多线程编程2---事件、临界区、Synchronize、WaitFor……

    接着上文介绍TThread. 现在开始说明 Synchronize和WaitFor 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区 事件(Event) 事件(Event)与De ...

  4. Delphi中的线程类--之(1)

    Delphi中的线程类 猛禽[Mental Studio] http://mental.mentsu.com ( 之一) Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数D ...

  5. 认识Delphi的线程类

    本文是没有写过delphi的多线程,对delphi6的线程类TThread不熟悉的人而写的,主要从 TThread的源代码入手.(其他版本的delphi,请参照此文自行理解) Delphi为多线程的实 ...

  6. Delphi中的线程类--之(2)

    Delphi中的线程类 猛禽[Mental Studio] http://mental.mentsu.com 之二 首先就是构造函数: constructor TThread.Create(Creat ...

  7. Delphi的线程类

    本文是没有写过delphi的多线程,对delphi的线程类TThread不熟悉的人而写的,主要从 TThread的源代码入手. Delphi为多线程的实现专门封装了一个TThread类来实现,我们从C ...

  8. Delphi中的线程类Thread

    原文:http://www.heibai.net/article/info/info.php?infoid=22594 Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数De ...

  9. 自定义线程池-线程类和任务类代码实现

    自定义线程池-实现步骤 1:编写任务类(MyTask),实现Runnable接口; 2:编写线程类(MyWorker),用于执行任务,需要持有所有任务; 3:编写线程池类(MyThreadPool), ...

最新文章

  1. HDU 4826 Labyrinth(DP解法)
  2. 基于区块链的健康链系统设计与实现(5)区块链性能优化
  3. socket 2.草稿。
  4. 使用SQL Server 2005 Report Builder
  5. LiteOS内核源码分析:动态内存之Bestfit分配算法
  6. linux 延展集群如何设置,OCaml 4.11.0 发布,将函数式语言 Caml 在面向对象上进行延展...
  7. 25.卷1(套接字联网API)--- 信号驱动式IO
  8. 13. 滑动时间窗口算法概念原理
  9. 图灵科普数学宝藏书单|购书狂欢618倒计时,这份书单闭眼入
  10. c语言进行catia二次开发,想入门CATIA二次开发CAA的盆友们(谈谈开发经验,或许对你有帮助)...
  11. CentOS下安装EDM工具
  12. 头条视频消重软件 免费批量修改视频md5
  13. Tushare库之获取股票列表接口
  14. jenkins svn publisher插件使用手册
  15. Mysql安装步骤方法
  16. STC硬件主板--电子乐谱展示的设计
  17. 共轭梯度法求解线性方程组Ax=b(附代码)
  18. 【产业互联网周报】2025年全球机器人市场将达到2485亿美元;孟鼎铭卸任,SAP公布新一代领导团队;...
  19. Cobalt Strike笔记(持续更新)
  20. win10编译OpenCV4Android系列1-Android编译环境搭建

热门文章

  1. word.interop
  2. 2023最新支付宝微信运动步数网页源码+附带原始接口
  3. 开源无线充电恒功率硬件电路
  4. 电动汽车充放电最优调度 研究了EV充电和放电的调度优化问题 我们首先制定全局调度优化问题,其中优化充电功率以最小化所有在白天执行充电和放电的EV的总成本
  5. 关于office2016 程序停止运行。。。的问题
  6. 【NOIP 2016】Day1 T2 天天爱跑步
  7. Qt TCP/IP(多客户端连接服务器)多个客户端同时登陆的聊天室示例
  8. 基于协同过滤推荐+余弦相似度算法实现新闻推荐系统
  9. meshgrid()+plt.contourf()用法
  10. 终于来了!知乎中秋礼盒