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 SynchronizeCheckSynchronizeWaitFor 会受到怎样的影响。

当我意识到 WaitFor 是 locking/hanging 时,我开始调查这个问题。在 WaitFor 内部,它检查 if CurrentThread.ThreadID = MainThreadID,然后重复检查 MsgWaitForMultipleObjects 的结果,并根据结果执行 CheckSynchronizePeekMessage,当它收到 WAIT_OBJECT_0 最终会退出循环。如果 MainThreadIDCurrentThreadID 不同,则 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 = MainThreadIDWaitFor 是打电话。

所以我怀疑在这种情况下我永远不会使用 SynchronizeWaitFor。这是正确的还是比我看到的更多?

如果根据我的假设,所谓的主线程可能根本没有调用 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 deadlocksDLLMain.

因为 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;