【问题标题】:Delphi Win API CreateTimerQueueTimer threads and thread safe FormatDateTime crashesDelphi Win API CreateTimerQueueTimer 线程和线程安全的 FormatDateTime 崩溃
【发布时间】:2008-12-09 12:05:21
【问题描述】:

这是一个有点长的问题,但我们开始吧。有一个 FormatDateTime 版本,据说是线程安全的,您可以使用

GetLocaleFormatSettings(3081, FormatSettings); 

获取一个值,然后你可以像这样使用它;

FormatDateTime('yyyy', 0, FormatSettings); 

现在想象两个定时器,一个使用 TTimer(间隔说 1000 毫秒),然后另一个定时器是这样创建的(间隔 10 毫秒);

CreateTimerQueueTimer
(
  FQueueTimer, 
  0, 
  TimerCallback, 
  nil, 
  10, 
  10, 
  WT_EXECUTEINTIMERTHREAD
);

现在是最后一点,如果在回调和计时器事件中你有以下代码;

for i := 1 to 10000 do
begin
  FormatDateTime('yyyy', 0, FormatSettings);
end;

注意没有分配。这几乎会立即产生访问冲突,有时会在 20 分钟后随机出现。现在,如果您在 C++Builder 中编写该代码,它永远不会崩溃。我们使用的标头转换是 JEDI JwaXXXX 的。即使我们在 Delphi 版本中围绕代码加锁,也只会延迟不可避免的情况。我们查看了原始的 C 头文件,一切看起来都不错,C++ 使用 Delphi 运行时有什么不同的方式吗? FormatDatTime 的线程安全版本看起来是可重入的。任何可能已经看过此内容的人的任何想法或想法。

更新:

为了缩小范围,FormatSettings 作为 const 传入,所以它们是否使用相同的副本是否重要(因为在函数调用中传递本地版本会产生同样的问题)?此外,采用 FormatSettings 的 FormatDateTime 版本不会调用 GetThreadLocale,因为它已经在 FormatSettings 结构中具有区域设置信息(我通过单步执行代码进行了仔细检查)。

我提到没有分配以明确表示没有访问共享存储,因此不需要锁定。

WT_EXECUTEINTIMERTHREAD 用于简化问题。我的印象是你应该只将它用于非常短的任务,因为如果它运行很长时间,它可能意味着它会错过下一个间隔?

如果您使用普通的旧 TThread,则不会出现问题。我想我在这里得到的是使用 TThread 或 TTimer 有效,但使用在 VCL 外部创建的线程不起作用,这就是为什么我问 C++ Builder 使用 VCL/Delphi RTL 的方式是否存在差异。

顺便说一句,前面提到的这段代码也失败了(但需要更长的时间),一段时间后,CS := TCriticalSection.Create;

  CS.Acquire;
  for i := 1 to LoopCount do
  begin
    FormatDateTime('yyyy', 0, FormatSettings);
  end;
  CS.Release;

现在我真的不明白,我按照建议写了这个;

function ReturnAString: string;
begin
  Result := 'Test';
  UniqueString(Result);
end;

然后在每种类型的定时器里面都有代码;

  for i := 1 to 10000 do
  begin
    ReturnAString;
  end;

这会导致相同类型的故障,正如我之前所说,故障永远不会出现在 CPU 窗口内的同一个地方等。有时它是访问冲突,有时它可能是无效的指针操作。我正在使用 Delphi 2009 顺便说一句。

更新 2:

Roddy(下)指出 Ontimer 事件(不幸的是 Winsock,即 TClientSocket)使用 Windows 消息泵(顺便说一句,使用 IOCP 和重叠 IO 有一些不错的 Winsock2 组件会很好),因此推送摆脱它。但是,有谁知道如何查看 CreateQueueTimerQueue 上设置了哪种线程本地存储?

感谢您花时间思考和回答这个问题。

【问题讨论】:

    标签: multithreading delphi winapi formatdatetime


    【解决方案1】:

    我不确定为我自己的问题发布“答案”是否是一种好的形式,但这似乎合乎逻辑,如果这不酷,请告诉我。

    我想我找到了问题所在,线程本地存储的想法引导我跟着一堆线索,我发现了这条神奇的线路;

    IsMultiThread := 真;

    来自帮助;

    “IsMultiThread 设置为 true 表示内存管理器应该支持多线程。IsMultiThread 由 BeginThread 和类工厂设置为 true。”

    这当然不是通过使用 TTimer 使用单个 Main VCL 线程来设置的,但是当您使用 TThread 时它是为您设置的。如果我手动设置它,问题就会消失。

    在 C++Builder 中,我不使用 TThread,但它使用以下代码出现;

    if (IsMultiThread) {
      ShowMessage("IsMultiThread is True!");
    }
    

    那是它自动为你设置的地方。

    我真的很高兴人们的意见,这样我就可以找到这个,我希望它可以帮助别人。

    【讨论】:

    • 如果你创建一个标准的Delphi线程,那么就没有必要设置这个值了。但有时,就像在一个被称为多线程的 DLL 中,你必须自己设置它。好地方。
    【解决方案2】:

    由于 FormatDateTime 调用的 DateTimeToString 使用 GetThreadLocale,您可能希望尝试为每个线程设置一个本地 FormatSettings 变量,甚至可以在循环之前在本地变量中设置 FormatSettings。

    也可能是导致此问题的 WT_EXECUTEINTIMERTHREAD 参数。请注意,它声明它只能用于非常短的任务。

    如果问题仍然存在,问题实际上可能出在其他地方,这是我看到此问题时的第一个预感,但我没有足够的信息来了解代码的作用,无法真正确定这一点。

    有关访问冲突发生位置的详细信息可能会有所帮助。

    【讨论】:

      【解决方案3】:

      您确定这实际上与FormatDateTime 有关吗?您特别提到那里没有赋值语句;这是你问题的一个重要方面吗?如果您改为调用其他字符串返回函数会发生什么? (确保它不是一个常量字符串。编写您自己的函数,在返回之前调用UniqueString(Result)。)

      FormatSettings 变量是线程特定的吗?这就是为FormatDateTime 提供额外参数的意义所在,因此每个线程都有自己的私有副本,保证在函数处于活动状态时不会被任何其他线程修改。

      定时器队列对这个问题重要吗?或者,当您使用普通的旧 TThread 并在 Execute 方法中运行循环时,您会得到相同的结果吗?

      您确实警告说这是一个很长的问题,但您似乎可以做一些事情来缩小问题范围,缩小问题的范围。

      【讨论】:

        【解决方案4】:

        我想知道您进行的 RTL/VCL 调用是否期望访问一些在您通过计时器队列调用代码时未正确设置的线程本地存储 (TLS) 变量?

        这不是您问题的答案,但您是否知道 TTimer OnTimer 事件只是作为 VCL 主线程中正常消息循环的一部分运行?

        【讨论】:

          【解决方案5】:

          您找到了答案 - IsMultiThread。必须随时使用它才能恢复使用 API 并创建线程。来自 MSDN: CreateTimerQueueTimer 正在创建一个线程池来处理此功能,因此您有一个外部线程与主 VCL 线程一起工作而没有保护。 (注意:你的 CS.acquire/release 根本不做任何事情,除非代码的其他部分尊重这个锁。)

          【讨论】:

          • 我不太清楚为什么“CS.acquire/release 没有做任何事情”,因为我认为它会在两个地方保护代码 FormatDateTime,但我想 Main VCL 正在使用我无法轻易控制并导致问题的其他地方的内存管理器。
          【解决方案6】:

          回复。您关于 Winsock 和重叠 I/O 的最后一个问题:您应该仔细查看 Indy

          Indy 使用阻塞 I/O,无论主线程在做什么,当您需要高性能网络 IO 时,它都是一个不错的选择。现在您已经解决了多线程问题,您应该创建另一个(或更多)线程来使用 indy 来处理您的 I/O。

          【讨论】:

            【解决方案7】:

            Indy 的问题在于,如果您需要很多连接,它根本就没有效率。每个连接需要一个线程(阻塞 I/O),这根本无法扩展,因此 IOCP 和重叠 IO 的好处,它几乎是 Windows 上唯一可扩展的方式。

            【讨论】:

            • 当然,但它通常是这样的 PITA,我认为在许多情况下它是过早的优化。不过,这个线程可能会为您提供一些建议。 stackoverflow.com/questions/37185
            【解决方案8】:

            对于更新2:

            有一个免费的 IOCP 套接字组件:http://www.torry.net/authorsmore.php?id=7131(包含源代码)

            “作者 Naberegnyh Sergey N.. 高 基于性能的套接字服务器 Windows 完成端口和使用 Windows 套接字扩展。 IPv6 支持的。 "

            我在寻找更好的组件/库来重新架构我的小型即时消息服务器时发现了它。我还没有尝试过,但作为第一印象,它看起来不错。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-10-14
              • 2015-02-15
              • 1970-01-01
              • 1970-01-01
              • 2010-11-17
              相关资源
              最近更新 更多