1

考虑以下 SO 问题: 在非 VCL 应用程序中使用同步是否危险?

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

当我意识到WaitFor正在锁定/挂起时,我开始对此进行调查。在WaitFor它内部检查if CurrentThread.ThreadID = MainThreadID然后重复检查结果,MsgWaitForMultipleObjects并根据它将要执行的结果,CheckSynchronize或者PeekMessage当它收到 a 时,WAIT_OBJECT_0它最终将退出循环。如果MainThreadID不一样的CurrentThreadIDWaitFor会做单的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

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

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

更新

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

Raymond Chen 还发表了一篇博客文章,讨论DLLMain.

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

4

1 回答 1

1

回顾一下:

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

Raymond Chen 还发表了一篇博客文章,讨论DLLMain.

因为DLLMain在卸载 DLL 以及线程退出时调用,所以我无法WaitFor在 DLL 卸载代码 ( OnWebModuleDestroy) 中执行线程,因为这显然会导致死锁,因为一个将等待另一个。

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

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

TApacheApplication(Application).OnTerminate := OnApplicationTerminate;
于 2021-03-08T08:43:28.567 回答