关于多线程:Windows SuspendThread不是吗? (GetThreadContext失败)

Windows SuspendThread doesn't? (GetThreadContext fails)

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

1
2
3
4
5
6
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系统错误代码" Access Denied")。

如果没有返回错误,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系统上看到这种行为的任何提示。还没有回到Wine实验。

2010年11月:奇怪。看来,如果我在VisualStudio 2005下进行编译,则它在Windows Vista和7上将失败,但在较早的OS上不会。如果我在VisualStudio 2010下编译,它不会在任何地方失败。也许有人会指责VisualStudio2005,但我对位置敏感问题感到怀疑,VS 2005和VS 2010中的不同优化器将代码放置在稍微不同的位置。

2012年11月:佐贺继续。我们在许多XP和Windows 7计算机上都以非常低的速度(每运行几千次)看到此故障。我们的Suspend活动适用于主要执行纯计算代码但有时会调用Windows的线程。我不记得当线程的PC位于我们的计算代码中时看到过这个问题。当然,当线程PC挂起时,我看不到它,因为GetContext不会将其提供给我,因此我无法直接确认该问题仅在执行系统调用时发生。但是,我们所有的系统调用都通过一个点进行引导,到目前为止,有证据表明该点是在我们遇到问题时执行的。因此,间接证据表明,只有在该线程正在执行系统调用时,该线程上的GetContext才会失败。我还没有精力去做一个关键的实验来检验这个假设。


让我引用Richter / Nassare的"通过C ++ 5Ed的Windows"可能会带来一些启发:

DWORD SuspendThread(HANDLE hThread);

Any thread can call this function to
suspend another thread (as long as you
have the thread's handle). It goes
without saying (but I'll say it
anyway) that a thread can suspend
itself but cannot resume itself. Like
ResumeThread, SuspendThread returns
the thread's previous suspend count. A
thread can be suspended as many as
MAXIMUM_SUSPEND_COUNT times (defined
as 127 in WinNT.h). Note that
SuspendThread is asynchronous with
respect to kernel-mode execution, but
user-mode execution does not occur
until the thread is resumed.

In real life, an application must be
careful when it calls SuspendThread
because you have no idea what the
thread might be doing when you attempt
to suspend it. If the thread is
attempting to allocate memory from a
heap, for example, the thread will
have a lock on the heap. As other
threads attempt to access the heap,
their execution will be halted until
the first thread is resumed.
SuspendThread is safe only if you know
exactly what the target thread is (or
might be doing) and you take extreme
measures to avoid problems or
deadlocks caused by suspending the
thread.

...

Windows actually lets you look inside
a thread's kernel object and grab its
current set of CPU registers. To do
this, you simply call
GetThreadContext:

BOOL GetThreadContext( HANDLE
hThread, PCONTEXT pContext);

To call this function, just allocate a
CONTEXT structure, initialize some
flags (the structure's ContextFlags
member) indicating which registers you
want to get back, and pass the address
of the structure to GetThreadContext.
The function then fills in the members
you've requested.

You should call SuspendThread before
calling GetThreadContext; otherwise,
the thread might be scheduled and the
thread's context might be different
from what you get back. A thread
actually has two contexts: user mode
and kernel mode. GetThreadContext can
return only the user-mode context of a
thread. If you call SuspendThread to
stop a thread but that thread is
currently executing in kernel mode,
its user-mode context is stable even
though SuspendThread hasn't actually
suspended the thread yet. But the
thread cannot execute any more
user-mode code until it is resumed, so
you can safely consider the thread
suspended and GetThreadContext will
work.

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

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

由于未记录此行为,因此建议与Microsoft联系。


旧问题,但很高兴看到您再经历2年以上后仍保持状态变化的最新信息。

问题的原因是,WoW64的x64版本的翻译层中存在错误,如下所示:

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

WoW64下的GetThreadContext中存在一个相当关键的错误,该错误使它返回陈旧的内容,从而使其在许多情况下不可用。内容以用户模式存储。这就是为什么您认为该值不为null,但在陈旧的内容中该值仍为null的原因。

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

至于为什么在Visual Studio 2010/2012上构建的解决方案中似乎很少发生此错误,则可能是编译器正在执行某些操作来缓解大多数问题,为此,您应检查从2005和2005生成的IL。 2010年,看看有什么区别。例如,如果构建的项目可能没有优化,是否会发生问题?

最后,进一步阅读:

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


围绕挂起拥有CriticalSection的线程有一些特殊的问题。我现在找不到很好的参考,但是在Raymond Chen的博客中有一个提及,而在Chris Brumme的博客中有另一个提及。基本上,如果在线程访问OS锁(例如堆锁,DllMain锁等)时不够幸运地调用SuspendThread,那么可能会发生真正的奇怪事情。我认为这种情况很少出现。

在处理器产生类似Sleep(0)的结果之后,重试对GetThreadContext的调用是否有效?


在拥有同步对象(例如互斥锁或关键节)的线程上调用SuspendThread会导致死锁,前提是该调用线程试图获取挂起线程拥有的同步对象。
-MSDN


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