2013-07-19 31 views
9

我一直在試圖追查內存泄漏絕地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) 

我已提起訴訟,它在我的大腦從未正確的已被燒燬後。是否有任何有效的用法讓線程立即啓動(當你需要初始化值時)?

+0

哇! JVCL D5!我雖然在我退出後關閉了它,並停止保持D5兼容性。這種懷舊的感覺... –

+1

@ Arioch'The不要太懷舊;它是從2009年開始的JVCL 3.x。嚴格來說,這是Richard Marquand從2005年開始的最初HidController類;我幫了一些忙。 JVCL採用的版本經歷了巨大的*「jcl-ifying」*;但沒有真正的差異;但從技術上說,我使用理查德的版本;所以我可以修復FastMM捕獲的* free-after-free *崩潰。 –

回答

12

這是Delphi 5實現TThread的基本設計缺陷。底層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及以後的問題。

+0

這解釋了它。爲歷史課程+1! –

+0

我總是這樣做的方式是直接在線程執行中實例化/銷燬。無論如何,如果你需要處理諸如COM之類的東西(比如ADO),就必須這樣做。所以,實際上,無論何時我寫一個線程,我都不會在create/destroy中實現任何創建或銷燬任何事情。 (+1) –

+0

@Jerry這很好,直到你需要在創建者和線程之間溝通 –