4

这是一个有点长的问题,但我们开始吧。有一个 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 上设置了哪种线程本地存储?

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

4

8 回答 8

5

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

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

IsMultiThread := 真;

从帮助;

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

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

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

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

那就是它自动为您设置的某个地方。

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

于 2008-12-10T01:34:34.660 回答
1

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

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

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

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

于 2008-12-09T15:41:02.207 回答
1

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

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

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

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

于 2008-12-09T16:10:10.587 回答
0

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

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

于 2008-12-10T00:00:51.013 回答
0

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

于 2008-12-10T03:17:12.550 回答
0

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

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

于 2008-12-10T08:50:43.330 回答
0

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

于 2008-12-10T10:17:17.790 回答
0

对于更新2:

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

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

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

于 2009-01-13T00:38:22.787 回答