【发布时间】:2021-03-03 08:54:29
【问题描述】:
考虑以下 SO 问题: Is it dangerous to use synchronize in a non-VCL application?
我想知道在 Apache Web 应用程序 DLL 中使用 TThread's Synchronize、CheckSynchronize 和 WaitFor 时会受到怎样的影响。
当我意识到WaitFor 被锁定/挂起时,我开始调查此问题。在WaitFor 内部,它检查if CurrentThread.ThreadID = MainThreadID,然后重复检查MsgWaitForMultipleObjects 的结果,并根据结果执行CheckSynchronize 或PeekMessage,当它收到WAIT_OBJECT_0 时,它最终将退出循环。如果MainThreadID 与CurrentThreadID 不同,则WaitFor 将执行单个WaitForSingleObject。
所以我创建了以下测试 Apache Web 模块 DLL 来查看 MainThreadID 在 Web 请求的生命周期内是什么。
type
TTheThread = class(TThread)
private
protected
procedure Execute; override;
public
end;
TWebModule1 = class(TWebModule)
procedure WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
public
end;
procedure DoLog(AMsg : String);
var
WebModuleClass: TComponentClass = TWebModule1;
TestThread : TTheThread;
Logger : TLogger;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
procedure DoLog(AMsg : String);
begin
AMsg := AMsg + #13#10 + 'MainThreadID: ' + InttoStr(MainThreadID) +
#13#10 + 'CurrThreadID: ' + InttoStr(GetCurrentThreadId);
Logger.Log(ltNormal,AMsg);
end;
procedure TTheThread.Execute;
begin
While not Terminated do
begin
Sleep(5000);
DoLog('Thread Execute - Inside While - Terminated: ' + BoolToStr(Terminated,True));
end;
DoLog('Thread Execute - End - Terminated: ' + BoolToStr(Terminated,True));
end;
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
Response.Content :=
'<html>' +
'<head><title>Web Server Application</title></head>' +
'<body>Web Server Application</body>' +
'</html>';
DoLog('WebModule1DefaultHandlerAction');
end;
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
Logger := TLogger.Create(nil);
{set some Logger properties}
DoLog('WebModuleCreate - Before Thread Create');
TestThread := TTheThread.Create(True);
TestThread.Start;
DoLog('WebModuleCreate - Created Thread');
end;
procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
DoLog('WebModuleDestroy Before Terminate');
TestThread.Terminate;
DoLog('WebModuleDestroy Before WaitFor');
TestThread.WaitFor;
DoLog('WebModuleDestroy Before Free');
TestThread.Free;
DoLog('WebModuleDestroy End');
Logger.Free;
end;
end.
所以我启动 Apache,在浏览器中访问网页,倒计时大约 12 秒,然后我停止 Apache。我给了它一段时间,但httpd 服务只破坏了几个线程,然后保持不变并且永远不会关闭。
这是日志:
2021-03-03 10:23:42 081 WebModuleCreate - Before Thread Create
2021-03-03 10:23:42 081 ^ MainThreadID: 2432
2021-03-03 10:23:42 081 ^ CurrThreadID: 6328
2021-03-03 10:23:42 081 WebModuleCreate - Created Thread
2021-03-03 10:23:42 081 ^ MainThreadID: 2432
2021-03-03 10:23:42 081 ^ CurrThreadID: 6328
2021-03-03 10:23:42 081 WebModule1DefaultHandlerAction
2021-03-03 10:23:42 081 ^ MainThreadID: 2432
2021-03-03 10:23:42 081 ^ CurrThreadID: 6328
2021-03-03 10:23:47 093 Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:47 093 ^ MainThreadID: 2432
2021-03-03 10:23:47 093 ^ CurrThreadID: 2220
2021-03-03 10:23:52 100 Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:52 100 ^ MainThreadID: 2432
2021-03-03 10:23:52 100 ^ CurrThreadID: 2220
2021-03-03 10:23:57 101 Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:57 101 ^ MainThreadID: 2432
2021-03-03 10:23:57 101 ^ CurrThreadID: 2220
2021-03-03 10:23:58 313 WebModuleDestroy Before Terminate
2021-03-03 10:23:58 313 ^ MainThreadID: 2432
2021-03-03 10:23:58 313 ^ CurrThreadID: 2432
2021-03-03 10:23:58 313 WebModuleDestroy Before WaitFor
2021-03-03 10:23:58 313 ^ MainThreadID: 2432
2021-03-03 10:23:58 313 ^ CurrThreadID: 2432
2021-03-03 10:24:02 108 Thread Execute - Inside While - Terminated: True
2021-03-03 10:24:02 108 ^ MainThreadID: 2432
2021-03-03 10:24:02 108 ^ CurrThreadID: 2220
2021-03-03 10:24:02 108 Thread Execute - End - Terminated: True
2021-03-03 10:24:02 108 ^ MainThreadID: 2432
2021-03-03 10:24:02 108 ^ CurrThreadID: 2220
如您所见,WaitFor 永远不会结束。有趣的是,OnWebModuleCreate 在与假定的MainThreadID 不同的线程中运行,但OnWebModuleDestroy 在相同的MainThreadID 中运行,因此在调用WaitFor 时CurrentThreadID = MainThreadID。
所以我怀疑在这种情况下我将永远不必使用Synchronize 和WaitFor。这是正确的还是比我看到的更多?
如果根据我的假设,所谓的主线程可能根本没有调用CheckSynchronize,我可以用什么来进行同步,我可以只做一个WaitForSingleObject而不是WaitFor吗?我怀疑这里可能根本不需要与主线程同步。
更新
经过大量阅读,我发现了有关 DLLMain/DLLProc 的信息,并且每当进程“附加/分离”并且线程“附加/分离”时都会调用它
还有一篇Raymond Chen的博文,discusses thread deadlocksDLLMain。
所以似乎因为DLLMain在卸载DLL时被调用,并且当线程退出时我不能在DLL卸载代码(OnWebModuleDestroy)中执行线程WaitFor,因为这显然会导致死锁因为一个人会等另一个人。如果有人能就此意见提出建议,我将不胜感激。
【问题讨论】:
-
我没有使用 Apache Webapplications 的经验,但是如果这些行为与 IIS 上的 ISAPI dll 相同,那么您需要在 Termination 回调例程
TerminateExtension中终止线程。