参考:http://www.cnblogs.com/rogee/archive/2010/09/20/1832053.html

  Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数的Delphi书籍都有讲到,但是基本上都是对TThread类的几个成员作一简单介绍,再说明一个 Execute的实现和 Synchronize 的用法就完了。然而这并不是多线程编程的全部。

  线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程,即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时,就是所谓的“多线程”

  那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)

  如果用Windows API来创建线程的话,是通过一个叫做 CreateThread的API函数来实现的,它的定义是

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程属性(用于在NT下进行线程的安全属性设置,在9X下无效)
DWORD dwStackSize, //堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, //起始地址
LPVOLD lpParameter, //参数
DWORD dwCreationFlags, //创建标志(用于设置线程创建时候的状态)
LPDWORD lpThreadId //线程ID
);

  最后返回线程 Handle。其中的起始地址就是线程函数的入口,直至线程函数结束,线程也就结束了

  因为CreateThread的参数很多,而且是Windows API,所以在 C Runtime Library里面提供了一个通用的线程函数(理论上是可以在任何支持线程的OS中使用):

unsigned long_beginthread(void(_USERENTRY *__start)(void*), unsigned __stksize, void * __arg);

  Delphi也提供了一个相同功能的类似函数

function BeginThread(SecurityAttributes: Pointer;
StackSize: LongWord;
ThreadFunc: TThreadFunc;
Parameter: Pointer;
CreationFlags: LongWord;
var ThreadId: LongWord): Integer;

  这三个函数的功能是基本相同的,它们都是将线程函数中大代码放到一个独立的线程中执行。线程函数与一般函数最大的不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的

  正常情况下,线程函数返回之后,线程就终止了。但是也有其他方式:

Windows API:

VOID ExitThread(DWORD dwExitCode);

C Runtime Library:

void _endthread(void);

Delphi Runtime Library:

procedure EndThread(ExitCode: Integer);

  

  为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如在Windows中那个Handle 便是这个内部Object的 Handle,所以在线程结束的时候还应该释放这个Object

  虽然说用API 和 RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此 Delphi在Classes单元中对线程做了一个较好的封装,这就是VCL的线程类: TThread

  使用这个类也很简单,大多数的Delphi都有说,基本用法是:

1.先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例)

2.然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分)

3.如果需要用到可视VCL对象,还需要通过Synchronize过程进行

  

  本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入探究一下TThread类的实现。因为只有真正地了解它,才能更好的使用它

  下面是Delphi 7 中TThread类的声明(本文只讨论 Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码)

TThread = class
private
FHandle: THandle;
FThreadID: THandle;
FCreateSuspended: Boolean;
FTerminated: Boolean;
FSuspended: Boolean;
FFreeOnTerminate: Boolean;
FFinished: Boolean;
FReturnValue: Integer;
FOnTerminate: TNotifyEvent;
FSynchronize: TSynchronizeRecord;
FFatalException: TObject;
procedure CallOnTerminate;
class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;
function GetPriority: TThreadPriority;
procedure SetPriority(Value: TThreadPriority);
procedure SetSuspended(Value: Boolean);
protected
procedure CheckThreadError(ErrCode: Integer); overload;
procedure CheckThreadError(Success: Boolean); overload;
procedure DoTerminate; virtual;
procedure Execute; virtual; abstract;
procedure Synchronize(Method: TThreadMethod); overload;
property ReturnValue: Integer read FReturnValue write FReturnValue;
property Terminated: Boolean read FTerminated;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure AfterConstruction; override;
procedure Resume;
procedure Suspend;
procedure Terminate;
function WaitFor: LongWord;
class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
property FatalException: TObject read FFatalException;
property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
property Handle: THandle read FHandle;
property Priority: TThreadPriority read GetPriority write SetPriority;
property Suspended: Boolean read FSuspended write SetSuspended;
property ThreadID: THandle read FThreadID;
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end;

  TThread类在Delphi的RTL里面算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件:OnTerminate做详细分析

  首先是构造函数

constructor TThread.Create(CreateSuspended: Boolean);
begin
inherited Create;
AddThread;
FSuspended:= CreateSuspended;
FCreateSuspended:= CreateSusPended;
FHandle:= BeginThread(nil, 0, @ThreadProc, Pointer(Self), Create_SUSPENDED, FThreadID);
if FHandle = 0 then
raise EThread.CreateRssFmt(@SThreadCreateError,[SysErrorMessage(GetLastError)]); end;

  虽然这个构造函数没有多少代码,但是却可以算是最重要的一个成员,因为线程就是在这里被创建的。

  在通过Inherited 调用TObject.Create之后,第一句就是调用一个过程:AddThread,其源码如下

procedure AddThread;
begin
InterlockedIncrement(ThreadCount);
end;

  同样有对应的RemoveThread

procedure RemoveThread;
begin
InterlockedDecrement(ThreadCount);
end;

  它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程,而是用InterlockedIncrement/InterlockedDecrement这对过程。,它们实现的功能完全一样,都是对变量加一或减一。但是它们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证执行结果的正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。以加一为例来说明二者实现细节上的不同:

  一般而言,对内存数据加一的操作分解之后有三步:

1)从内存读出数据

2)数据加一

3)存入内存

  现在假设在一个有两个线程的进程中用Inc进行加一操作,可能出现下面这种情况:

1)线程A从内存中读出数据(假设为3)

2)线程B也从内存中读出数据(也是3)

3)线程A对数据加一(现在是4)

4)线程B对数据加一(现在也是4)

5)线程A将数据存入内存(现在内存中的数据是4)

6)线程B也将数据存入内存(现在的内存中的数据还是4,但是两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果

  而用InterlockedIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个“原语”执行完毕之前不会进行线程切换。所以在上面的例子中,只有当线程A执行完并将数据存入到内存之后,线程B才可以开始从中取数并进行加一操作,这就保证了即使是在多线程情况下,结果一定会使正确的。

  前面的那个例子也说明一种“线程访问冲突”的情况,这也是为什么线程之间需要“同步”(Synchronize),关于这个,在后面说到同步的时候还会再详细讨论

  说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议,个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一个是传统意义上的同步(To occur at the same time),另一个是“协调一致”(To operate in unison)。在“线程同步”中的Synchronize一词应该是指后面一种意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不过像这样译得不准的词在IT业还有很多,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的,绝不能含糊。

  扯远了,回到TThread的构造函数上,接下来最重要的就是这句了

FHandle:= BeginThread(nil, 0, @ThreadProc(Self), Create_SUSPENDED, FThreadID);

  这里就用到了前面所说的Delphi RTL函数BeginThread,它有很多参数,关键是第三、四两个参数。第三个参数就是前面说到的线程函数,即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数,这里就是创建的线程对象(即Self)。其他的参数中,第五个是用于设置线程在创建之后即挂起,不立即执行(启动线程的工作实在 AfterConstruction中根据 CtreateSuspended标识来决定的),第六个是返回线程ID

  现在来看 TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码

fucntion ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException:= AcquireExceptionObject;
end;
finally
FreeThread:= Thread.FFreeOnTerminate;
Result:= Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished:= True;
SignalSyncEvent;
if FreeThread then
Thread.Free;
EndThread(Result);
end;
end;

  虽然这里也没有多少代码,但却是整个TThread中最重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码做逐行说明:

  首先判断线程类的Terminated 标识,如果未被标志位终止,则调用线程类的Execute方法执行线程代码,因为TThread是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。

  所以说,Execute 就是线程类中的线程函数,所有在Execute 中的代码都需要被当做线程代码来考虑。如防止访问冲突等。如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中

  最后是线程结束之前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminate属性的设置,然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的 DoTerminate方法

  DoTerminate方法的代码如下

procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then
Synchronize(CallOnTerminate);
end;

  很简单,就是通过Synchronize 来调用 CallOnTerminate 方法,而CallOnTerminate 方法的代码如下,就是简单地调用OnTerminate 事件:

procedure TThread.CallOnTerminate;
begin
if Assigned(FOnTerminate) then
FOnTerminate(Self);
end;

  因为 OnTerminate事件是在 Synchronize 中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对Synchronize的分析)

  执行完OnTerminate之后,将线程类的 FFinished标志设置为True。接下来执行SignalSyncEvent过程,其代码如下

procedure SignalSyncEvent
begin
SetEvent(SyncEvent);
end;

  也很简单,就是设置一下一个全局变量Event:SyncEvent,关于Event 的使用,本文将在后文详述,而SyncEvent 的用途将在 WaitFor过程中说明

  然后根据 FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些操作,详见下面的析构函数实现。

  最后调用 EndThread 结束线程,返回线程返回值。至此,线程完全结束。

  说完构造函数,再看析构函数

destructor TThread.Destory;
begin
if (FThreadID <> 0) and not FFinished then
begin
Terminate;
if FCreateSuspended then
Resume;
WaitFor;
end;
if FHandle <> 0 then
CloseHandle(FHandle);
inherited Destory;
FFatalException.Free;
RemoveThread;
end;

  在线程对象被释放之前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设置),则调用Terminate 过程结束线程。Terminate 过程知识简单地设置线程类的 Terminated标志,如下面的代码:

procedure TThread.Terminate;
begin
FTerminated:= True;
end;

  所以线程仍然必须继续执行到正常结束之后才行,而不是立即终止线程,这一点要注意

  在这里说一些题外话:很多人问过我,如何才能“立即”终止线程(当前是指用TThread 创建的线程)。结果当然是不行!终止线程的唯一的方法就是让Execute 方法执行完毕,所以一般来说,要让你的线程能尽快终止,必须在Execute 方法中在较短的时间内不断检查Terminated标志,以便能及时地退出。这是设计线程代码的一个很重要的原则!

  当然如果你一定要能“立即”退出线程,那么TThread 类不是一个好的选择,因为如果用API强制终止线程的话,最终会导致TThread 线程对象不能被正确释放,在对象析构时出现 Access Violation。这种情况你只能使用API或者RTL函数来创建线程

  如果线程结束处于启动挂起状态,则线程转入到运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现,将放到后面说明。

  线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。

  然后调用TObject.Destory 释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减少进程的线程数

  其他关于 Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面讨论的是本文的另两个重点:Synchronize和WaitFor。具体的见下一篇博客

Delphi中线程类TThread实现多线程编程1---构造、析构……的更多相关文章

  1. 转发 Delphi中线程类TThread 实现多线程编程

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

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

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

  3. 转:学习笔记: Delphi之线程类TThread

    学习笔记: Delphi之线程类TThread - 5207 - 博客园http://www.cnblogs.com/5207/p/4426074.html 新的公司接手的第一份工作就是一个多线程计算 ...

  4. 学习笔记: Delphi之线程类TThread

    新的公司接手的第一份工作就是一个多线程计算的小系统.也幸亏最近对线程有了一些学习,这次一接手就起到了作用.但是在实际的开发过程中还是发现了许多的问题,比如挂起与终止的概念都没有弄明白,导致浪费许多的时 ...

  5. Delphi 实现多线程编程的线程类 TThread

    http://blog.csdn.net/henreash/article/details/3183119 Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉 ...

  6. Delphi中的线程类 - TThread详解

    Delphi中的线程类 - TThread详解 2011年06月27日 星期一 20:28 Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本 ...

  7. 线程和Python—Python多线程编程

    线程和Python 本节主要记录如何在 Python 中使用线程,其中包括全局解释器锁对线程的限制和对应的学习脚本. 全局解释器锁 Python 代码的执行是由 Python 虚拟机(又叫解释器主循环 ...

  8. Delphi中TStringList类常用属性方法详解

    TStrings是一个抽象类,在实际开发中,是除了基本类型外,应用得最多的. 常规的用法大家都知道,现在来讨论它的一些高级的用法. 先把要讨论的几个属性列出来: 1.CommaText 2.Delim ...

  9. Java中线程的使用 (2)-多线程、线程优先级、线程睡眠、让步、阻塞

    Java中线程的使用 (2)-多线程.线程优先级.线程睡眠.让步.阻塞 (一)多线程使用方法 说明:创建每个新的线程,一定要记得启动每个新的线程(调用.start()方法) class Xc3 ext ...

随机推荐

  1. Kali Linux渗透基础知识整理(四):维持访问

    Kali Linux渗透基础知识整理系列文章回顾 维持访问 在获得了目标系统的访问权之后,攻击者需要进一步维持这一访问权限.使用木马程序.后门程序和rootkit来达到这一目的.维持访问是一种艺术形式 ...

  2. ndk学习15: IPC机制

    Linux IPC机制 来自为知笔记(Wiz)

  3. 插入备份数据 报 IDENTITY_INSERT 为 ON 时解决方法

    SQL Server 2008 R2 x64 下 事情是这样的, 使用Navcate的数据导出备份工具导出表的记录 INSERT INTO [xxx_users] VALUES (1, 'tom ', ...

  4. 向SqlServer数据库插入数据

    Insert Values Insert Select Insert Exec Select Into Bulk Insert Insert Values是最常用的一种插入数据的方式,基本语法如下,表 ...

  5. centos7时间同步和时区设置

    centos7时间同步和时区设置 安装ntp服务的软件包 sudo yum install ntp 将ntp服务设置为缺省启动 systemctl enable ntpd 修改启动参数,增加-g -x ...

  6. php利用svn hooks将程序自动发布到测试环境

    利用svn hooks将php程序自动发布到测试环境 复制仓库hooks目录下的post-commit.tmpl为post-commit cp post-commit.tmpl post-commit ...

  7. windows系统下ftp上传下载和一些常用命令

    先假设一个ftp地址 用户名 密码 FTP Server: home4u.at.china.com User: yepanghuang Password: abc123 打开windows的开始菜单, ...

  8. 我刚知道的WAP app中meta的属性(转载)

    之前我一直做的都是WEB前端开发,来北京以后面试了一个移动前端开发,WAP前端开发. 其实在原来公司的时候也做过这方面的开发,可面试的时候面试官问我,要想强制让文档与设备的宽度保持1:1,mate标签 ...

  9. C# 对象深度拷贝

    转载 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using ...

  10. 【XLL 文档翻译】【第3部分】必要的和有用的 C API XLM 函数

    本节中将介绍几个对于 DLL 和 XLL 开发人员来说十分重要的回调函数,xlfRegister 函数是可用于注册函数,使得 Excel 可以直接访问 DLL 和 XLl 中的函数. xlfUnreg ...