为什么我的帖子被关键部分阻止而没有被任何东西占据?

dla*_*nod 6 c++ multithreading critical-section

我遇到了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
Run Code Online (Sandbox Code Playgroud)

行数据等都表示进入特定的关键部分.唯一的问题是没有其他线程似乎将此关键部分保持打开状态.Windbg的!locks命令没有任何指示,并且转储临界区表示它没有被锁定,因为null所有者和下面结构中的-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
Run Code Online (Sandbox Code Playgroud)

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

dla*_*nod 8

事实证明,在没有相应的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 
Run Code Online (Sandbox Code Playgroud)

当执行后续的EnterCriticalSection时,它挂起,因为RecursionCount非零 - 如果RecursionCount为0,则线程只能获取临界区的所有权.但是它确实增加了LockCount(将其恢复为原始问题中看到的-1)只是为了混淆问题.

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

至于导致它的代码:

if (SysStringLen(bstrState) > 0)
    CHECKHR_CS( m_pStateManager->SetState(bstrState), &m_csStateManagerLock );
Run Code Online (Sandbox Code Playgroud)

以及错误检查宏的定义:

#define CHECKHR_CS(x, cs)                       \
    EnterCriticalSection(cs);                       \
    if( FAILED(hr = (x)) ) {                        \
        LeaveCriticalSection(cs);                   \
        return hr;                          \
    }                           \
    LeaveCriticalSection(cs);
Run Code Online (Sandbox Code Playgroud)

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

  • 考虑使用[RAII惯用法](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)并在构造函数中包装`EnterCriticalSection()`和在析构函数中包含`LeaveCriticalSection()`.这样你就不会忘记解锁互斥锁(或解锁它两次).这就是[Boost的`lock_guard`](http://www.boost.org/doc/libs/1_48_0/doc/html/thread/synchronization.html#thread.synchronization.locks.lock_guard)的工作原理. (3认同)