【问题标题】:Access Violation in Multithreaded Application on Windows Server 2003Windows Server 2003 上的多线程应用程序中的访问冲突
【发布时间】:2010-12-17 20:03:01
【问题描述】:

我有一个应用程序(使用 Delphi 2009 编写)允许用户在选定的系统上运行查询,然后将结果合并到一个报告中。

应用简介:
用户选择一个查询和一组系统来运行查询。查询在所有系统上同时运行,方法是创建一个新线程来运行查询,并使用 TADOQuery 从线程内实际运行查询。当查询完成运行时,调用 TADOQuery.SaveToFile,将 pfXML 作为参数传递以将结果保存到 XML 文档。一旦所有查询完成运行,应用程序就会解析所有 XML 文档并将它们合并到一个 XML 文档中。然后用户可以加载报告,它调用 TADOQuery.LoadFromFile 来加载报告并将其显示在 TListView 中。

为了确保用户不会因为提交太多查询(从而启动太多线程)而使 PC 过载,我使用记录数组实现了一个队列。每条记录包含查询名称、系统、状态(正在运行、已完成或待处理)等信息。实现队列的另一个原因是用户可以同时提交多个查询(即他们不必等待第一个在提交另一个之前完成)。记录数组可能不是实现队列的最有效方式,但它确实有效。我将并发运行的线程数保持为 100(这可以由用户更改),并在运行线程结束时启动新线程,方法是将线程内的查询运行的完成与管理队列的过程同步。内存使用量绝不会增加大约 25-30K。

最后一条相关信息是应用程序还包含一个作业调度程序,它允许用户指定他们希望何时运行查询。这适用于希望让软件在服务器上无人看管地运行并在每天的特定时间每天创建报告的客户。

问题:
该应用程序在 Windows XP 上运行良好。无论提交多少查询。但是,在 Windows Server 2003 上运行一段时间后,应用程序将停止运行。尝试与应用程序交互(或将其关闭)会导致报告访问冲突。我这辈子都搞不清楚它是从哪里来的,或者是什么原因造成的。

我的第一个想法是它可能与操作系统中内存管理的实现有关,但我看不出可能导致问题的原因。我已经在完全调试模式下使用 FastMM4 编译了应用程序的一个版本,但它没有报告任何关于释放不应释放的内存或任何类似问题的问题,并且在正常情况下运行时没有内存泄漏,所以虽然我仍然确定内存管理存在问题,但我看不出那会是什么。

我注意到在访问冲突发生后,我的应用程序的 temp 文件夹中有很多报告文件(这意味着一些查询已经运行并返回了结果,但并非所有线程都已完成并且报告尚未合并)。作业调度程序还报告它一直运行良好并将作业提交到队列中,因为最大线程数(默认为 100)正在运行(我一直在通过调度程序每 20 次提交作业进行测试)分钟并让应用程序过夜)。

这让我相信队列(记录数组)的处理可能存在问题。如果一个线程完成并与管理队列的过程同步,但队列有问题,则不会启动下一个线程,并且由于并非所有查询都已完成,因此临时文件夹中的报告胜出'不被巩固。这似乎是应用程序卡住的地方。

因此我有两个问题:
1. 是什么导致访问冲突?是与队列有关,还是可能是其他原因?
2. 为什么应用程序在 Windows XP 上运行良好,但在 Windows Server 2003 上运行失败?

更新
在测试过程中,我已经成功地把应用程序打到了这样的程度,以至于我现在也可以在 Windows XP 上产生错误,所以它看起来并不局限于 Windows Server 2003。它似乎只是出现在 Windows Server 2003 上比 XP 快很多。

如果我在一组系统上运行一组查询,请等到所有报告都已创建,然后重复该过程,最终,查询将停止提交并双击应用程序中的任意位置(并尝试关闭) 导致访问冲突(始终写入,尽管内存地址不同,并且并不总是写入地址 0)。

我已经使用 MadExcept 跟踪了调用堆栈,它没有显示任何异常 - 只有触发双击事件的代码行。

某些东西正在阻止正在提交的查询,并且(我猜)也导致了访问冲突,但我看不出它可能是什么。

【问题讨论】:

  • 访问冲突是否有错误地址?您可以在源代码中查找该地址吗?是读违规还是写违规?如果它被读取,它是读取 0、一个合理的数字还是一个神奇的常数?您是否尝试过在异常发生时使用 MadExcept/JCL/etc 跟踪调用堆栈?
  • 它有一个错误地址,但每次都不一样。这是一个读取冲突,读取值为 0。我还没有尝试跟踪调用堆栈,因为我之前没有这样做过(在 Delphi 之外),但我会试一试。
  • 我一直在使用 MadExcept 来跟踪调用堆栈,它没有标记任何异常。有关更多信息,请参阅上面的更新。

标签: multithreading arrays delphi


【解决方案1】:

更多的是故障排除技巧,而不是实际答案... 对于#2,硬件是否相同?如果不是,这可能真的是单核与多核(或处理器)的问题。由于它是一个多线程应用程序,因此期望它在多个处理器/内核上表现不同并不是没有道理的。因此,请确保您没有用太多变量(硬件与操作系统)来混淆问题。

【讨论】:

  • 是的,相同的硬件。我们还让应用程序在运行 Win2k3 的不同硬件上运行,结果相同,因此它看起来与操作系统相关,而不是硬件问题。
  • 您的队列中确实出现了死锁,您可以尝试将关联性设置为一个 CPU/核心,看看问题是否保持不变。
【解决方案2】:

我终于设法找到了问题所在,我想你可以称之为小学生错误!

在每个线程中,我创建一个用于运行查询的 TADOQuery 组件。我将所有者设置为主窗体,但是在线程终止之前,我清除了内存:

adoqry := TADOQuery.create(frmMain);
try
  <code>
finally
  freeAndNil(adoqry);
end;

似乎问题在于将 TADOQuery 组件的所有者设置为应用程序的主屏幕,这意味着主屏幕也会在应用程序关闭时尝试释放内存,但考虑到应用程序一直在运行当用户运行数千个查询时,这些引用似乎堆积起来,最终应用程序放弃并开始引发访问冲突。

我已将 TADOQuery 组件的所有者更改为 nil,现在应用程序运行良好,即使在数千次查询运行后(最初大约一百次后它就崩溃了)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多