【问题标题】:Synchronize, CheckSynchronize and WaitFor in Apache Web Application DLLApache Web 应用程序 DLL 中的同步、CheckSynchronize 和 WaitFor
【发布时间】:2021-03-03 08:54:29
【问题描述】:

考虑以下 SO 问题: Is it dangerous to use synchronize in a non-VCL application?

我想知道在 Apache Web 应用程序 DLL 中使用 TThread's SynchronizeCheckSynchronizeWaitFor 时会受到怎样的影响。

当我意识到WaitFor 被锁定/挂起时,我开始调查此问题。在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 中运行,因此在调用WaitForCurrentThreadID = MainThreadID

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

如果根据我的假设,所谓的主线程可能根本没有调用CheckSynchronize,我可以用什么来进行同步,我可以只做一个WaitForSingleObject而不是WaitFor吗?我怀疑这里可能根本不需要与主线程同步。

更新

经过大量阅读,我发现了有关 DLLMain/DLLProc 的信息,并且每当进程“附加/分离”并且线程“附加/分离”时都会调用它

还有一篇Raymond Chen的博文,discusses thread deadlocksDLLMain

所以似乎因为DLLMain在卸载DLL时被调用,并且当线程退出时我不能在DLL卸载代码(OnWebModuleDestroy)中执行线程WaitFor,因为这显然会导致死锁因为一个人会等另一个人。如果有人能就此意见提出建议,我将不胜感激。

【问题讨论】:

  • 我没有使用 Apache Webapplications 的经验,但是如果这些行为与 IIS 上的 ISAPI dll 相同,那么您需要在 Termination 回调例程 TerminateExtension 中终止线程。

标签: apache delphi


【解决方案1】:

回顾一下:

每当进程“附加/分离”并且线程“附加/分离”时,都会调用 DLL 的 DLLMain/DLLProc

还有一篇 Raymond Chen 的博文,discusses thread deadlocksDLLMain

因为 DLLMain 在 DLL 被卸载以及线程退出时被调用,所以我不能在 DLL 卸载代码 (OnWebModuleDestroy) 中执行线程 WaitFor,因为这显然会导致死锁。等待对方。

在 R.Hoek 发表评论之后,我开始查看 Apache 在实际卸载模块 DLL 并遇到其 apr_pool_cleanup 和伴随的 apr_pool_cleanup_register 过程之前是否调用了某种清理过程,该过程将钩子注册到清理中.幸运的是,这已经在Web.ApacheApp 单元及其TApacheApplication 类中得到处理,该类有一个OnTerminate“事件”,由apr_pool_cleanup 调用。

将清理代码分配给OnTerminate“事件”可以让您正确摆脱线程,而不必担心DLLMain中的死锁:

TApacheApplication(Application).OnTerminate := OnApplicationTerminate;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-11
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 2015-02-15
    • 2015-12-27
    • 1970-01-01
    相关资源
    最近更新 更多