VC ++ 2010:奇怪的临界区错误

时间:2011-08-19 15:18:58

标签: c++ windows multithreading visual-c++ critical-section

我的程序在我可以重现的小场景中随机崩溃,但它发生在来自ntdll.dll的mlock.c(这是一个VC ++运行时文件)中,我看不到堆栈跟踪。我知道它发生在我的一个线程函数中。

这是程序崩溃的mlock.c代码:

void __cdecl _unlock (
        int locknum
        )
{
        /*
         * leave the critical section.
         */
        LeaveCriticalSection( _locktable[locknum].lock );
}

错误是“指定的句柄无效”。如果我看一下locknum,它的数量大于_locktable的大小,所以这是有道理的。

这似乎与关键部分的使用有关。我在我的线程中使用CRITICAL_SECTIONS,通过CCriticalSection包装器类及其相关的RAII保护器CGuard。 here的定义可以避免更加混乱。

这是崩溃的线程函数:

unsigned int __stdcall CPlayBack::timerThread( void * pParams ) {
#ifdef _DEBUG
    DRA::CommonCpp::SetThreadName( -1, "CPlayBack::timerThread" );
#endif
    CPlayBack * pThis = static_cast<CPlayBack*>( pParams );
    bool bContinue = true;
    while( bContinue ) {
        float m_fActualFrameRate = pThis->m_fFrameRate * pThis->m_fFrameRateMultiplier;
        if( m_fActualFrameRate != 0 && pThis->m_bIsPlaying ) {
            bContinue = ( ::WaitForSingleObject( pThis->m_hEndThreadEvent, static_cast<DWORD>( 1000.0f / m_fActualFrameRate ) ) == WAIT_TIMEOUT );
            CImage img;
            if( pThis->m_bIsPlaying && pThis->nextFrame( img ) )
                pThis->sendImage( img );
        }
        else
            bContinue = ( ::WaitForSingleObject( pThis->m_hEndThreadEvent, 10 ) == WAIT_TIMEOUT );
    }
    ::GetErrorLoggerInstance()->Log( LOG_TYPE_NOTE, "CPlayBack", "timerThread", "Exiting thread" );
    return 0;
}

CCriticalSection在哪里进来?每个CImage对象都包含一个CCriticalSection对象,它通过CGuard RAII锁使用。而且,每个CImage都包含一个实现引用计数的CSharedMemory对象。为此,它还包含两个CCriticalSection,一个用于数据,一个用于引用计数器。在析构函数中可以看到这些交互的一个很好的例子:

CImage::~CImage() {
    CGuard guard(m_csData);
    if( m_pSharedMemory != NULL ) {
        m_pSharedMemory->decrementUse();
        if( !m_pSharedMemory->isBeingUsed() ){
            delete m_pSharedMemory;
            m_pSharedMemory = NULL;
        }
    }
    m_cProperties.ClearMin();
    m_cProperties.ClearMax();
    m_cProperties.ClearMode();
}

CSharedMemory::~CSharedMemory() {
    CGuard guardUse( m_cs );
    if( m_pData && m_bCanDelete ){
        delete []m_pData;
    }
    m_use = 0;
    m_pData = NULL;
}

有人遇到过这种错误吗?有什么建议吗?

编辑:我看到了一些调用堆栈:调用来自~CSharedMemory。所以必须有一些竞争条件

修改:更多CSharedMemory代码here

3 个答案:

答案 0 :(得分:5)

“指定的无效句柄”返回代码描绘了一个非常清晰的图片,表明您的关键部分对象已被解除分配;当然假设它是在开始时正确分配的。

您的RAII课程似乎可能是罪魁祸首。如果您退后一步并考虑一下,您的RAII类违反了Sepration Of Concerns原则,因为它有两个工作:

  1. 它为CRITICAL_SECTION提供分配/销毁语义
  2. 它为CRITICAL_SECTION提供获取/释放语义
  3. 我看到的CS包装器的大多数实现都以相同的方式违反了SoC原则,但它可能会有问题。特别是当您必须开始传递类的实例以获得获取/释放功能时。考虑一下psudocode中一个简单,人为的例子:

    void WorkerThreadProc(CCriticalSection cs)
    {
      cs.Enter();
      // MAGIC HAPPENS
      cs.Leave();
    }
    
    int main()
    {
      CCriticalSection my_cs;
      std::vector<NeatStuff> stuff_used_by_multiple_threads;
    
      // Create 3 threads, passing the entry point "WorkerThreadProc"
      for( int i = 0; i < 3; ++i )
        CreateThread(... &WorkerThreadProc, my_cs);
    
      // Join the 3 threads...
      wait(); 
    }
    

    这里的问题是CCriticalSection是按值传递的,因此析构函数被调用4次。每次调用析构函数时,都会释放CRITICAL_SECTION。第一次工作正常,但现在已经不见了。

    你可以通过将引用或指针传递给关键部分类来解决这个问题,但是你会因为所有权问题而混淆语义水域。如果“拥有”暴击秒的线程在其他线程之前消失怎么办?您可以使用shared_ptr,但现在没有人真正“拥有”关键部分,并且您已经放弃了对区域的一点控制,以便在另一个区域获得一点点。

    这个问题的真正“解决”是分开关注。有一个课程用于分配&amp;解除分配:

    class CCriticalSection : public CRITICAL_SECTION
    {
    public:
      CCriticalSection(){ InitializeCriticalSection(this); }
      ~CCriticalSection() { DestroyCriticalSection(this); }
    };
    

    ...和另一个处理锁定&amp;解锁...

    class CSLock
    {
    public:
      CSLock(CRITICAL_SECTION& cs) : cs_(cs) { EnterCriticalSection(&cs_); }
      ~CSLock() { LeaveCriticalSection(&cs_); }
    private: 
      CRITICAL_SECTION& cs_;
    };
    

    现在,您可以传递原始指针或对单个CCriticalSection对象(可能是const)的引用,并让工作线程在其上实例化自己的CSLocks。 CSLock由创建它的线程拥有,这应该是应有的,但CCriticalSection的所有权显然由某个控制线程保留;也是一件好事。

答案 1 :(得分:1)

  • 确保关键部分对象不在#pragma包装1(或任何非默认包装)中。
  • 确保没有其他线程(或同一线程)损坏CS对象。运行一些静态分析工具来检查是否存在任何缓冲区溢出问题。
  • 如果您有运行时分析工具,请运行它以查找问题。

答案 2 :(得分:1)

我决定坚持KISS原则,并且摇滚所有nite 简化事情。我想我会将CSharedMemoryClass替换为std::tr1::shared_ptr<BYTE>CCriticalSection,以保护其免受并发访问。两者现在都是CImage的成员,现在关注点更好,恕我直言。

这解决了奇怪的关键部分,但现在看来我有std::tr1::shared_ptr引起的内存泄漏,你可能会看到我很快发布它......它永远不会结束!