我一直试图追踪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)
我已将其归档于我的大脑中,因为从不正确。是否有任何有效的用途让线程立即启动(当你必须初始化值时)?
答案 0 :(得分:12)
这是使用TThread
的Delphi 5实现的基本设计缺陷。底层Windows线程在TThread
的构造函数中启动。这导致你所描述的种族。
在Delphi 6版本的RTL中,线程启动机制已更改。从Delphi 6开始,该线程在TThread.AfterConstruction
中启动。并且在构造函数完成后运行。这将使您的代码免费竞争。
在Delphi 6及更高版本中,底层Windows线程在TThread
构造函数中创建,但使用CREATE_SUSPENDED
标志创建暂停。然后在AfterConstruction
中,只要TThread.FCreateSuspended
为False
,线程就会恢复。
在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及更高版本中的问题。