多核时代下的多线程编程1 转 doorkey博客

多核时代下的多线程编程----帮大家读懂TThread

TThread类在DelphiRTL里算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件: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.CreateResFmt(@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不能。或者按操作系统理论中的术语来说,这是一对原语操作。

 

 

接下来最重要就是这句了:

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

 

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

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

 

function 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中最重要的部分因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明:

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

2Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。

3、如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。

4、最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置,然后将线程返回值设置为线程类的返回值属性的值。

5、接下来执行线程类的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;

 

6SignalSyncEvent也很简单就是设置一下一个全局EventSyncEvent关于Event的使用SyncEvent的用途将在WaitFor过程中说明。

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

8、最后调用EndThread结束线程,返回线程返回值。

至此,线程完全结束。

 

 

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

destructor TThread.Destroy;

begin

  if (FThreadID <> 0) and not FFinished then

  begin

    Terminate;

    if FCreateSuspended then

      Resume;

    WaitFor;

  end;

  RemoveQueuedEvents(Self, nil);

  if FHandle <> 0 then CloseHandle(FHandle);   //关闭线程Handle

  inherited Destroy;

  FFatalException.Free;

  RemoveThread;

end;

 

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

 

procedure TThread.Terminate;

begin

FTerminated := True;

end;

 

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

然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,

最后调用RemoveThread减小进程的线程数。

 

 

 

至此我们知道了线程要结束必须等待线程的Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在Execute方法中在较短的时间内不断地检查Terminated标志,以便能及时地退出。这是设计线程代码的一个很重要的原则!

 

 

原文地址:https://www.cnblogs.com/luckForever/p/7254587.html