9

我一直在尝试追踪 Jedi VCL 中的内存泄漏JvHidControllerClass.pas,我在源历史记录中遇到了这种变化:

旧版本:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice);
begin
  inherited Create(True);
  Device := Dev;
  NumBytesRead := 0;
  SetLength(Report, Dev.Caps.InputReportByteLength);
end;

当前版本:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice);
begin
  inherited Create(False);
  Device := Dev;
  NumBytesRead := 0;
  SetLength(Report, Dev.Caps.InputReportByteLength);
end;

根据经验,我发现如果您创建一个挂起的线程:

inherited Create(False);

然后线程立即开始运行。在这种情况下,它将尝试访问尚未初始化的对象:

procedure TJvHidDeviceReadThread.Execute;
begin
   while not Terminated do
   begin
     FillChar(Report[0], Device.Caps.InputReportByteLength, #0);
     if Device.ReadFileEx(Report[0], Device.Caps.InputReportByteLength, @DummyReadCompletion) then

立即尝试填充Report并访问该对象Device。问题是它们还没有被初始化;这些是线程开始后的下一行:

  Device := Dev;
  NumBytesRead := 0;
  SetLength(Report, Dev.Caps.InputReportByteLength);

我意识到这是一种竞争条件;并且用户在生产中遇到崩溃的可能性非常低,因此离开赛车崩溃可能是无害的。

但我走远了吗?我错过了什么吗?是否调用:

BeginThread(nil, 0, @ThreadProc, Pointer(Self), Flags, FThreadID);

不启动线程并立即运行?这真的是(故意)添加到 JVCL 中的竞争条件回归吗?有什么秘密吗

CreateSuspended(False);

这使它成为正确的代码:

CreateSuspended(True);
...
FDataThread.Resume;

?

在被误叫而被烧毁后

TMyThread.Create(False)

我已经将它归档在我的脑海中,因为它永远不会正确。让线程立即启动(当您必须初始化值时)是否有任何有效用途?

4

1 回答 1

12

这是 Delphi 5 实现的基本设计缺陷TThread。底层 Windows 线程在TThread. 这导致了你描述的比赛。

在 RTL 的 Delphi 6 版本中,线程启动机制发生了变化。从 Delphi 6 开始,线程开始于TThread.AfterConstruction. 并且在构造函数完成后运行。这将使您的代码竞赛免费。

在 Delphi 6 及更高版本中,底层 Windows 线程是在构造函数中创建的,但创建时使用该标志TThread挂起。CREATE_SUSPENDED然后在 中AfterConstruction,只要TThread.FCreateSuspendedFalse,线程就会恢复。

在 Delphi 5 中解决该问题的一种方法是最后调用继承的构造函数。像这样:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice);
begin
  Device := Dev;
  NumBytesRead := 0;
  SetLength(Report, Dev.Caps.InputReportByteLength);
  inherited Create(False);
end;

我知道相当丑陋。

因此,一旦构造函数完成,您创建暂停和恢复线程的方法可能会更好。这种方法反映了 RTL 如何解决 Delphi 6 及更高版本中的问题。

于 2013-07-19T14:21:53.987 回答