5

我们有一个 Windows32 应用程序,其中一个线程可以通过执行 SuspendThread/GetThreadContext/ResumeThread 来停止另一个线程以检查其状态 [PC 等]。

if (SuspendThread((HANDLE)hComputeThread[threadId])<0)  // freeze thread
   ThreadOperationFault("SuspendThread","InterruptGranule");
CONTEXT Context, *pContext;
Context.ContextFlags = (CONTEXT_INTEGER | CONTEXT_CONTROL);
if (!GetThreadContext((HANDLE)hComputeThread[threadId],&Context))
   ThreadOperationFault("GetThreadContext","InterruptGranule");

极少数情况下,在多核系统上,GetThreadContext 返回错误代码 5(Windows 系统错误代码“访问被拒绝”)。

SuspendThread 文档似乎清楚地表明目标线程被挂起,如果没有返回错误。我们正在检查 SuspendThread 和 ResumeThread 的返回状态;他们从来没有抱怨过。

我怎么能挂起一个线程,但不能访问它的上下文?

这个博客 http://www.dcl.hpi.uni-potsdam.de/research/WRK/2009/01/what-does-suspendthread-really-do/

表明 SuspendThread 在返回时可能已经开始挂起另一个线程,但该线程尚未挂起。在这种情况下,我可以看到 GetThreadContext 会有问题,但这似乎是定义 SuspendThread 的愚蠢方式。(SuspendThread 的调用如何知道目标线程实际挂起的时间?)

编辑:我撒谎了。我说这是针对 Windows 的。

好吧,奇怪的事实是,我在 Windows XP 64 下看不到这种行为(至少在上周没有,而且我真的不知道在那之前发生了什么)......但我们一直在测试这个 Windows 应用程序Ubuntu 10.x 上的 Wine。当尝试获取线程状态由于某种原因失败时,GetThreadContext的核心的Wine 源在第 819 行包含访问拒绝返回响应。我在猜测,但 Wine GetThreadStatus 似乎认为一个线程可能无法重复访问。为什么在 SuspendThead 超出我的范围后这是真的,但是有代码。想法?

EDIT2:我又撒谎了。我说我们只看到了 Wine 上的行为。不……我们现在发现了一个似乎产生相同错误的 Vista Ultimate 系统(再次,很少)。因此,Wine 和 Windows 似乎就一个不起眼的案例达成了一致。似乎仅启用 Sysinternals Process 监控程序会使情况恶化并导致问题出现在 Windows XP 64 上;我怀疑是海森堡。(过程监视器甚至在我用于开发的品酒 (:-) 机器或 XP 64 系统上都不存在)。

它到底是什么?

EDIT3:2010 年 9 月 15 日。我已经对 SuspendThread、ResumeThread 和 GetContext 的错误返回状态进行了仔细检查,而不会干扰代码。自从我这样做以来,我没有在 Windows 系统上看到任何有关这种行为的迹象。还没有回到葡萄酒实验。

2010 年 11 月:奇怪。似乎如果我在 VisualStudio 2005 下编译它,它在 Windows Vista 和 7 上会失败,但在早期的操作系统上不会。如果我在 VisualStudio 2010 下编译,它不会在任何地方失败。有人可能会指责 VisualStudio2005,但我怀疑位置敏感问题,VS 2005 和 VS 2010 中的不同优化器将代码放置在稍微不同的位置。

2012 年 11 月:传奇继续。我们在许多 XP 和 Windows 7 机器上看到了这种故障,而且发生率非常低(每几千次运行一次)。我们的 Suspend 活动适用于主要执行纯计算代码但有时会调用 Windows 的线程。我不记得当线程的 PC 在我们的计算代码中时看到这个问题。当然,当线程挂起时我看不到线程的PC,因为GetContext不会给我,所以我无法直接确认问题只发生在执行系统调用时。但是,我们所有的系统调用都通过一个点进行引导,到目前为止,有证据表明,当我们遇到问题时,该点已被执行。因此,间接证据表明,只有在该线程正在执行系统调用时,线程上的 GetContext 才会失败。我没有

4

5 回答 5

4

让我引用 Richter/Nassare 的“ Windows via C++ 5Ed ”,这可能会有所启发:

DWORD SuspendThread(HANDLE hThread);

任何线程都可以调用这个函数来挂起另一个线程(只要你有线程的句柄)。不言而喻(但我还是会这么说)一个线程可以挂起自己但不能恢复自己。与 ResumeThread 一样,SuspendThread 返回线程之前的挂起计数。一个线程可以暂停多达 MAXIMUM_SUSPEND_COUNT 次(在 WinNT.h 中定义为 127)。请注意,SuspendThread 相对于内核模式执行是异步的,但在线程恢复之前不会发生用户模式执行。

在现实生活中,应用程序在调用 SuspendThread 时必须小心,因为当您尝试挂起线程时,您不知道线程可能在做什么。例如,如果线程试图从堆中分配内存,则线程将在堆上锁定。当其他线程尝试访问堆时,它们的执行将暂停,直到第一个线程恢复。仅当您确切知道目标线程正在做什么(或可能正在做什么)并且您采取极端措施避免挂起线程导致的问题或死锁时,SuspendThread 才是安全的。

...

Windows 实际上允许您查看线程的内核对象并获取其当前的 CPU 寄存器集。为此,您只需调用 GetThreadContext:

BOOL GetThreadContext( HANDLE hThread, PCONTEXT pContext);

要调用此函数,只需分配一个 CONTEXT 结构,初始化一些标志(该结构的 ContextFlags 成员)指示您要取回哪些寄存器,并将该结构的地址传递给 GetThreadContext。然后该函数会填写您请求的成员。

您应该在调用 GetThreadContext 之前调用 SuspendThread;否则,线程可能会被调度,并且线程的上下文可能与您返回的不同。一个线程实际上有两个上下文:用户模式和内核模式。GetThreadContext 只能返回线程的用户模式上下文。如果您调用 SuspendThread 来停止线程,但该线程当前正在内核模式下执行,那么即使 SuspendThread 实际上还没有挂起线程,它的用户模式上下文也是稳定的。但是线程在恢复之前无法执行任何用户模式代码,因此您可以放心地认为线程已暂停并且 GetThreadContext 将起作用。

我的猜测是,如果你只是调用 SuspendThread,GetThreadContext 可能会失败,而线程处于内核模式,此时内核正在锁定线程上下文块。

也许在多核系统上,一个内核正在处理它的用户模式刚刚挂起的线程的内核模式执行,保持锁定线程的 CONTEXT 结构,恰好在另一个内核正在调用 GetThreadContext 时。

由于没有记录此行为,我建议联系 microsoft。

于 2010-08-18T18:53:51.397 回答
3

挂起拥有CriticalSection. 我现在找不到很好的参考资料,但在 Raymond Chen 的博客上提到过一次,在 Chris Brumme 的博客上也提到过一次。基本上,如果您不幸SuspendThread在线程访问操作系统锁(例如,堆锁、DllMain锁等)时调用,那么可能会发生非常奇怪的事情。我认为这是您极少遇到的情况。

GetThreadContext在处理器产生后重试调用是否可以工作Sleep(0)

于 2010-08-09T21:33:55.203 回答
3

旧问题,但很高兴看到您在遇到该问题 2 年多后仍然保持更新状态变化。

你的问题的原因是魔兽64的x64版本的翻译层有一个bug,根据:

http://social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/1558e9ca-8180-4633-a349-534e8d51cf3a

在 WoW64 下的 GetThreadContext 中有一个相当严重的错误,这使得它返回过时的内容,这使得它在许多情况下无法使用。内容存储在用户模式这就是为什么您认为该值不为空但在陈旧的内容中它仍然为空的原因。

这就是为什么它在较新的操作系统而不是较旧的操作系统上失败的原因,请尝试在 Windows 7 32 位操作系统上运行它。

至于为什么在 Visual Studio 2010 / 2012 构建的解决方案中这个错误似乎不太经常发生,很可能编译器正在做一些事情来缓解大部分问题,为此你应该检查从 2005 和2010,看看有什么不同。例如,如果项目是在没有优化的情况下构建的,是否会出现问题?

最后,进一步阅读:

http://www.nynaeve.net/?p=129

于 2013-04-07T07:38:47.427 回答
0

如果调用线程尝试获取挂起线程拥有的同步对象,则在拥有同步对象(例如互斥锁或临界区)的线程调用SuspendThread可能导致死锁- MSDN

于 2014-02-04T14:25:46.947 回答
0

也许是线程安全问题。你确定 hComputeThread 结构没有从你下面改变吗?当您调用挂起时,线程可能正在退出?这可能会导致挂起成功,但是当您调用 get context 时,它已经消失并且句柄无效。

于 2010-08-20T17:15:44.907 回答