学习笔记: Delphi之线程类TThread

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

在Delphi的VCL中封装了一个TThread类用于多线程的开发,这样比较符合面向对象的思想,同时又可以提高开发效率,一般的情况下开发都是通过派生这个类来实现多线程。所以重点还在这个类TThread上:

简单的看一眼,这个类倒也简单,就是封装了线程的API,通过一个ThreadProc函数来完成了多线程整个过程。很容易就能派生一个线程类:

TMyThread = class(TThread)
  private
    FCount: Integer;
    FOnShowValue: TGetStrProc;
  protected
    procedure Execute; override;
  public
    constructor Create;
    property Count: Integer read FCount;
    property OnShowValue: TGetStrProc read FOnShowValue write FOnShowValue;
  end;

{ TMyThread }
 
constructor TMyThread.Create;
begin
  inherited Create(False);
  FCount := 0;
  FreeOnTerminate := False;
end;
 
procedure TMyThread.Execute;
begin
  while not self.Terminated do
  begin
    Inc(FCount);
 
    if Assigned(FOnShowValue) then
      FOnShowValue(IntToStr(FCount));
 
    Sleep(100);
  end;
end;

代码中只覆盖了一个Execute方法即可,其他的代码都是业务相关的代码,还是非常简单好用。

线程挂起

线程还支持挂起的功能,即让CPU将线程中断,保留现场,不再分配时间片,这样线程就像死了一般,直到再次唤醒线程再恢复现场继续执行。

线程终止

在Delphi的TThread类实现中,可以通过一个Terminate方法来让线程终止。但事实上Terminated只是一个标识而已,在线程启动时这个标识为False。

线程释放

一般线程创建后运行完会自动释放,所以这里的类里我设置FreeOnTerminate := False;,这样线程对象就不会自动释放,这样做的好处就是可以由线程对象以外的代码来管理线程的生命周期,如果有的代码需要控制线程对象的生命周期就可以用这个属性来让线程不自己释放。

ThreadProc-源代码分析

function ThreadProc(Thread: TThread): Integer;
var
  FreeThread: Boolean;
begin
{$IFDEF LINUX}
  if Thread.FSuspended then sem_wait(Thread.FCreateSuspendedSem);
{$ENDIF}
  try
    if not Thread.Terminated then
    try
      Thread.Execute;
    except
      Thread.FFatalException := AcquireExceptionObject;
    end;
  finally
    FreeThread := Thread.FFreeOnTerminate;
    Result := Thread.FReturnValue;
    Thread.FFinished := True;
    Thread.DoTerminate;
    if FreeThread then Thread.Free;
{$IFDEF MSWINDOWS}
    EndThread(Result);
{$ENDIF}
{$IFDEF LINUX}
    // Directly call pthread_exit since EndThread will detach the thread causing
    // the pthread_join in TThread.WaitFor to fail.  Also, make sure the EndThreadProc
    // is called just like EndThread would do. EndThreadProc should not return
    // and call pthread_exit itself.
    if Assigned(EndThreadProc) then
      EndThreadProc(Result);
    pthread_exit(Pointer(Result));
{$ENDIF}
  end;
end;

对于TThread的一个关键部分就是这个ThreadProc方法,它是线程创建时传给系统API的回调函数;Delphi中通过这个方法完成了一个核心的功能,可以看到代码中调用了Execute方法。这也就是为什么派生类只要覆写这个方法的原因。

所以从代码也可以看出,线程启动后代码是顺序执行的,代码走完就结束了,所以为了让线程能够一直在运行就要在Execute方法里加上一个死循环,保证线程一直在运算,直到接收到Terminated时才让线程结束掉。所以Terminated的真正作用在这呢,需要开发者自己来控制,当然这样也就变的非常灵活了。