zoukankan      html  css  js  c++  java
  • Delphi中线程类TThread实现多线程编程1---构造、析构……

    参考: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。具体的见下一篇博客

  • 相关阅读:
    20155313 杨瀚 《网络对抗技术》实验九 Web安全基础
    20155313 杨瀚 《网络对抗技术》实验八 Web基础
    20155313 杨瀚 《网络对抗技术》实验七 网络欺诈防范
    20155313 杨瀚 《网络对抗技术》实验六 信息搜集与漏洞扫描
    20155313 杨瀚 《网络对抗技术》实验五 MSF基础应用
    20155313 杨瀚 《网络对抗技术》实验四 恶意代码分析
    20155313 杨瀚 《网络对抗技术》实验三 免杀原理与实践
    20155313 杨瀚 《网络对抗技术》实验二 后门原理与实践
    20155313 杨瀚 《网络对抗技术》实验一 PC平台逆向破解(5)M
    20155313 2017-2018-1 《信息安全系统设计基础》课程总结
  • 原文地址:https://www.cnblogs.com/xumenger/p/4449397.html
Copyright © 2011-2022 走看看