【发布时间】:2015-02-06 17:34:03
【问题描述】:
我读过的所有内容都表明 TRTTIContext 是线程安全的。
但是,TRTTIContext.FindType 似乎在多线程时偶尔会失败(返回 nil)。在它周围使用 TCriticalSection 可以解决此问题。请注意,我使用的是 XE6,而 XE 中似乎不存在该问题。 编辑:似乎存在于所有具有新 RTTI 单元的 Delphi 版本中。
我设计了一个测试项目,您可以自己查看。创建一个新的 VCL 项目,删除一个 TMemo 和一个 TButton,将 unit1 替换为下面,并分配 Form1.OnCreate、Form1.OnDestroy 和 Button1.OnClick 事件。关键 CS 是 TTestThread.Execute 中的 GRTTIBlock。目前已禁用,当我使用 200 个线程运行时,我会遇到 3 到 5 次失败。启用 GRTTIBlock CS 可消除故障。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, SyncObjs, Contnrs, RTTI;
type
TTestThread = class(TThread)
private
FFailed: Boolean;
FRan: Boolean;
FId: Integer;
protected
procedure Execute; override;
public
property Failed: Boolean read FFailed;
property Ran: Boolean read FRan;
property Id: Integer read FId write FId;
end;
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FThreadBlock: TCriticalSection;
FMaxThreadCount: Integer;
FThreadCount: Integer;
FRanCount: Integer;
FFailureCount: Integer;
procedure Log(AStr: String);
procedure ThreadFinished(Sender: TObject);
procedure LaunchThreads;
end;
var
Form1: TForm1;
implementation
var
GRTTIBlock: TCriticalSection;
{$R *.dfm}
{ TTestThread }
procedure TTestThread.Execute;
var
ctx : TRTTIContext;
begin
// GRTTIBlock.Acquire;
try
FFailed := not Assigned(ctx.FindType('Unit1.TForm1'));
FRan := True;
finally
// GRTTIBlock.Release;
end;
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
Randomize;
LaunchThreads;
Log(Format('Threads: %d, Ran: %d, Failures: %d',
[FMaxThreadCount, FRanCount, FFailureCount]));
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FThreadBlock := TCriticalSection.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FThreadBlock.Free;
end;
procedure TForm1.Log(AStr: String);
begin
Memo1.Lines.Add(AStr);
end;
procedure TForm1.ThreadFinished(Sender: TObject);
var
tt : TTestThread;
begin
tt := TTestThread(Sender);
Log(Format('Thread %d finished', [tt.Id]));
FThreadBlock.Acquire;
try
Dec(FThreadCount);
if tt.Failed then
Inc(FFailureCount);
if tt.Ran then
Inc(FRanCount);
finally
FThreadBlock.Release;
end;
end;
procedure TForm1.LaunchThreads;
var
c : Integer;
ol : TObjectList;
t : TTestThread;
begin
FRanCount := 0;
FFailureCount := 0;
FMaxThreadCount := 200;
ol := TObjectList.Create(False);
try
// get all the thread objects created and ready
for c := 1 to FMaxThreadCount do
begin
t := TTestThread.Create(True);
t.FreeOnTerminate := True;
t.OnTerminate := ThreadFinished;
t.Id := c;
ol.Add(t);
end;
FThreadCount := FMaxThreadCount;
// start them all up
for c := 0 to ol.Count - 1 do
begin
TTestThread(ol[c]).Start;
Log(Format('Thread %d started', [TTestThread(ol[c]).Id]));
end;
repeat
Application.ProcessMessages;
FThreadBlock.Acquire;
try
if FThreadCount <= 0 then
Break;
finally
FThreadBlock.Release;
end;
until False;
finally
ol.Free;
end;
end;
initialization
GRTTIBlock := TCriticalSection.Create;
finalization
GRTTIBlock.Free;
end.
【问题讨论】:
-
我个人认为最好有一个全局上下文变量。似乎 FThreadBlock 没有任何作用。您将受益于此处的
TList<T>而不是TObjectList。 -
ctx 是从池中创建的。它有一个引用计数机制,它的创建和销毁由 AtomicCmpExchange 调用保护。您在此测试中对系统施加了很大的压力。顺便说一句,FThreadBlock 总是在主线程中使用,并且不需要。
-
@Deltics,来自类单元:procedure TThread.DoTerminate;如果已分配(FOnTerminate)则开始,然后同步(CallOnTerminate);结束;
-
@Deltics:
TThread.Terminate()根本没有同步。它所做的只是直接为TThread.Terminated属性赋值。TThread.DoTerminate()在工作线程的上下文中调用,而TThread.OnTerminate在主线程的上下文中调用(除非您覆盖DoTerminate()以直接调用OnTerminate)。线程析构函数在线程上下文是否调用线程对象上的Free/Destroy()时调用线程析构函数,FreeOnTerminate为真时为工作线程本身,否则为不同的线程上下文。 -
这个问题在 Delphi 10 Seattle 中仍然存在
标签: multithreading delphi rtti delphi-xe6 win64