【问题标题】:Delphi: Should a thread ever be created "not suspended"?Delphi:是否应该“不暂停”创建线程?
【发布时间】:2013-07-19 14:17:15
【问题描述】:

我一直在尝试追踪 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。问题是它们还没有被初始化;这些是线程启动后的 next 行:

  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)

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

【问题讨论】:

  • 哇!!! D5 上的 JVCL!我虽然在我退出它并停止维护 D5 兼容性后它被关闭了。好怀旧的感觉……
  • @Arioch'The 不要太怀旧;它是 2009 年的 JVCL 3.x。严格来说,它是 Richard Marquand 从 2005 年开始的原始 HidController 类;我帮了一点忙。 JVCL采用的版本经历了巨大的"jcl-ifying";但没有真正的区别;但从技术上讲,我使用的是理查德的版本;所以我可以修复 FastMM 捕获的 use-after-free 崩溃。

标签: multithreading delphi delphi-5 jvcl


【解决方案1】:

这是TThread 的 Delphi 5 实现的一个基本设计缺陷。底层 Windows 线程在TThread 的构造函数中启动。这导致了你描述的比赛。

在Delphi 6 版本的RTL 中,线程启动机制发生了变化。从 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 及更高版本中的问题。

【讨论】:

  • 这就解释了。 +1 历史课!
  • 我一直这样做的方式是直接在线程执行中实例化/销毁。如果您需要使用 COM(例如 ADO)之类的东西,无论如何都必须这样做。所以,实际上,每当我写一个线程时,我从来没有在创建/销毁中实现任何创建或销毁或任何与此相关的事情。 (+1)
  • @Jerry 没关系,直到您需要在创建者和线程之间进行通信
  • "在 Delphi 5 中解决此问题的一种方法是最后调用继承的构造函数" - 另一种方法是复制 TThread 在 D6+ 中所做的事情。在构造函数中使用CreateSuspended=True 调用inherited(然后顺序无关紧要),然后覆盖AfterConstruction() 以调用Resume()。而不是要求构造线程对象的调用代码手动调用Resume()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多