Apache Web 应用程序 DLL 中的同步、检查同步和等待
Synchronize, CheckSynchronize and WaitFor in Apache Web Application DLL
考虑以下 SO 问题:
Is it dangerous to use synchronize in a non-VCL application?
我想知道在 Apache Web 应用程序 DLL 中使用 TThread's Synchronize
、CheckSynchronize
和 WaitFor
会受到怎样的影响。
当我意识到 WaitFor
是 locking/hanging 时,我开始调查这个问题。在 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
中运行,因此 CurrentThreadID = MainThreadID
当 WaitFor
是打电话。
所以我怀疑在这种情况下我永远不会使用 Synchronize
和 WaitFor
。这是正确的还是比我看到的更多?
如果根据我的假设,所谓的主线程可能根本没有调用 CheckSynchronize
,我可以使用什么来进行同步,我可以只执行一个 WaitForSingleObject
而不是 WaitFor
?我怀疑这里可能根本不需要主线程同步。
更新
经过大量阅读,我发现了有关 DLLMain/DLLProc
的信息,只要进程为“attached/detached”且线程为“attached/detached”,就会调用它。 48=]
DLLMain
.
中还有 Raymond Chen 的博客 post discusses thread deadlocks
所以似乎是因为 DLLMain
在 DLL 卸载时被调用以及线程退出时我无法在 DLL 卸载代码中执行线程 WaitFor
(OnWebModuleDestroy
) 因为这显然会导致死锁,因为一个人将等待另一个人。如果有人可以就此意见提出建议,我将不胜感激。
回顾一下:
只要进程为“attached/detached”且线程为“attached/detached”
,就会调用 DLL 的 DLLMain/DLLProc
Raymond Chen 也有一篇博客 post discusses thread deadlocks 在 DLLMain
.
因为 DLLMain
在 DLL 卸载时以及线程退出时被调用,所以我不能在 DLL 卸载代码 (OnWebModuleDestroy
) 中执行线程 WaitFor
,因为那样会显然会导致死锁,因为一个将等待另一个。
在 R.Hoek 发表评论后,我开始查看 Apache 在实际卸载模块 DLL 之前是否调用了某种清理程序,并发现了它的 apr_pool_cleanup
和同伴 apr_pool_cleanup_register
过程,它在清理中注册了一个钩子。幸运的是,这已经在 Web.ApacheApp
单元及其 TApacheApplication
class 中得到处理,它有一个 OnTerminate
“事件”,由 apr_pool_cleanup
调用。
将清理代码分配给 OnTerminate
“事件”允许您正确地摆脱线程而不用担心 DLLMain
:
中的死锁
TApacheApplication(Application).OnTerminate := OnApplicationTerminate;
考虑以下 SO 问题: Is it dangerous to use synchronize in a non-VCL application?
我想知道在 Apache Web 应用程序 DLL 中使用 TThread's Synchronize
、CheckSynchronize
和 WaitFor
会受到怎样的影响。
当我意识到 WaitFor
是 locking/hanging 时,我开始调查这个问题。在 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
中运行,因此 CurrentThreadID = MainThreadID
当 WaitFor
是打电话。
所以我怀疑在这种情况下我永远不会使用 Synchronize
和 WaitFor
。这是正确的还是比我看到的更多?
如果根据我的假设,所谓的主线程可能根本没有调用 CheckSynchronize
,我可以使用什么来进行同步,我可以只执行一个 WaitForSingleObject
而不是 WaitFor
?我怀疑这里可能根本不需要主线程同步。
更新
经过大量阅读,我发现了有关 DLLMain/DLLProc
的信息,只要进程为“attached/detached”且线程为“attached/detached”,就会调用它。 48=]
DLLMain
.
所以似乎是因为 DLLMain
在 DLL 卸载时被调用以及线程退出时我无法在 DLL 卸载代码中执行线程 WaitFor
(OnWebModuleDestroy
) 因为这显然会导致死锁,因为一个人将等待另一个人。如果有人可以就此意见提出建议,我将不胜感激。
回顾一下:
只要进程为“attached/detached”且线程为“attached/detached”
,就会调用 DLL 的DLLMain/DLLProc
Raymond Chen 也有一篇博客 post discusses thread deadlocks 在 DLLMain
.
因为 DLLMain
在 DLL 卸载时以及线程退出时被调用,所以我不能在 DLL 卸载代码 (OnWebModuleDestroy
) 中执行线程 WaitFor
,因为那样会显然会导致死锁,因为一个将等待另一个。
在 R.Hoek 发表评论后,我开始查看 Apache 在实际卸载模块 DLL 之前是否调用了某种清理程序,并发现了它的 apr_pool_cleanup
和同伴 apr_pool_cleanup_register
过程,它在清理中注册了一个钩子。幸运的是,这已经在 Web.ApacheApp
单元及其 TApacheApplication
class 中得到处理,它有一个 OnTerminate
“事件”,由 apr_pool_cleanup
调用。
将清理代码分配给 OnTerminate
“事件”允许您正确地摆脱线程而不用担心 DLLMain
:
TApacheApplication(Application).OnTerminate := OnApplicationTerminate;