为啥我的线程被临界区阻塞,没有被任何东西占用?

Posted

技术标签:

【中文标题】为啥我的线程被临界区阻塞,没有被任何东西占用?【英文标题】:Why is my thread blocked by a critical section not being held by anything?为什么我的线程被临界区阻塞,没有被任何东西占用? 【发布时间】:2012-01-12 04:09:58 【问题描述】:

我遇到了 C++ 中的关键部分的问题。我得到了一个挂起的窗口,当我转储进程时,我可以看到线程在关键部分等待:

  16  Id: b10.b88 Suspend: 1 Teb: 7ffae000 Unfrozen
ChildEBP RetAddr  
0470f158 7c90df3c ntdll!KiFastSystemCallRet
0470f15c 7c91b22b ntdll!NtWaitForSingleObject+0xc
0470f1e4 7c901046 ntdll!RtlpWaitForCriticalSection+0x132
0470f1ec 0415647e ntdll!RtlEnterCriticalSection+0x46

行数据等都表示进入特定的临界区。唯一的问题是似乎没有其他线程保持此关键部分处于打开状态。 Windbg 的 !locks 命令没有任何指示,转储关键部分表明它没有被锁定,如下面结构中的空所有者和 -1 LockCount 所示。

0:016> dt _RTL_CRITICAL_SECTION 42c2318
_RTL_CRITICAL_SECTION
   +0x000 DebugInfo        : 0x02c8b318 _RTL_CRITICAL_SECTION_DEBUG
   +0x004 LockCount        : -1
   +0x008 RecursionCount   : -1
   +0x00c OwningThread     : (null) 
   +0x010 LockSemaphore    : 0x00000340 
   +0x014 SpinCount        : 0

0:016> dt _RTL_CRITICAL_SECTION_DEBUG 2c8b318
_RTL_CRITICAL_SECTION_DEBUG
   +0x000 Type             : 0
   +0x002 CreatorBackTraceIndex : 0x2911
   +0x004 CriticalSection  : 0x042c2318 _RTL_CRITICAL_SECTION
   +0x008 ProcessLocksList : _LIST_ENTRY [ 0x2c8b358 - 0x2c8b2e8 ]
   +0x010 EntryCount       : 1
   +0x014 ContentionCount  : 1
   +0x018 Flags            : 0xbaadf00d
   +0x01c CreatorBackTraceIndexHigh : 0xf00d
   +0x01e SpareWORD        : 0xbaad

这怎么可能?即使在另一个线程没有调用 LeaveCriticalSection 的死锁中,我也希望看到临界区本身被标记为锁定。有没有人有任何调试建议或可能的修复?

【问题讨论】:

我要检查的一件事是我是否先完成了一个 EnterCriticalSection,然后是 2 个 LeaveCriticalSection。 检查临界区没有被删除。来自DeleteCriticalSection:如果一个临界区在它仍然拥有的情况下被删除,等待被删除临界区所有权的线程的状态是未定义的。 @hmjd 可能是正确的 0xbaadf00d mead 执行了解除分配。 不幸的是,这似乎不是原因。调用 DeleteCriticalSection 的唯一位置是在将临界区作为成员变量的对象的析构函数中。我添加了登录到析构函数只是为了确保它确认析构函数在此之前没有被意外调用。 【参考方案1】:

原来是在没有相应 EnterCriticalSection 的情况下调用 LeaveCriticalSection 的错误。这导致临界区将 LockCount 和 RecursionCount 递减为以下状态(LockCount 的默认值为 -1,RecursionCount 为 0):

0:016> dt _RTL_CRITICAL_SECTION 1092318
_RTL_CRITICAL_SECTION
    +0x000 DebugInfo        : 0x....... _RTL_CRITICAL_SECTION_DEBUG
    +0x004 LockCount        : -2
    +0x008 RecursionCount   : -1
    +0x00c OwningThread     : (null)
    +0x010 LockSemaphore    : 0x....... 
    +0x014 SpinCount        : 0 

当执行后续的 EnterCriticalSection 时,它挂起,因为 RecursionCount 不为零 - 如果 RecursionCount 为 0,线程只能获得临界区的所有权。但是它确实增加了 LockCount(将其返回到我在我的原始问题)只是为了混淆问题。

总而言之,如果您看到一个关键部分在 LockCount 和 RecursionCount 均为 -1 的情况下停止线程,则表示解锁过多。

至于导致它的代码:

if (SysStringLen(bstrState) > 0)
    CHECKHR_CS( m_pStateManager->SetState(bstrState), &m_csStateManagerLock );

以及错误检查宏的定义:

#define CHECKHR_CS(x, cs)                       \
    EnterCriticalSection(cs);                       \
    if( FAILED(hr = (x)) )                         \
        LeaveCriticalSection(cs);                   \
        return hr;                          \
                               \
    LeaveCriticalSection(cs);

宏在其内容周围缺少花括号,因此不满足 if 语句只会跳过 EnterCriticalSection。显然是个问题。

【讨论】:

考虑使用the RAII idiom 并将EnterCriticalSection() 包装在构造函数中,将LeaveCriticalSection() 包装在析构函数中。这样您就不会忘记解锁互斥锁(或解锁两次)。这就是Boost's lock_guard 的工作原理。 好点。如果在这里使用未声明的变量,它会引发编译器错误。

以上是关于为啥我的线程被临界区阻塞,没有被任何东西占用?的主要内容,如果未能解决你的问题,请参考以下文章

高并发基础

多线程小结

进程间通讯,临界区,互斥

Linux内核自旋锁

线程同步(windows平台):临界区

Linux驱动之同步互斥阻塞的应用