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

dar*_*mos 5 c++ windows multithreading critical-section visual-c++

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

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

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

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

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

这是崩溃的线程函数:

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;
}
Run Code Online (Sandbox Code Playgroud)

哪里CCriticalSection来的?每个CImage对象都包含一个CCriticalSection通过CGuardRAII锁使用的对象.而且,每个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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:此处有更多CSharedMemory代码

Joh*_*ing 5

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

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

  1. 它为CRITICAL_SECTION提供了分配/销毁语义
  2. 它为CRITICAL_SECTION提供了获取/释放语义

我见过的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(); 
}
Run Code Online (Sandbox Code Playgroud)

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

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

这个问题的真正"解决方案"是分离关注点.有一个分配和解除分配的类:

class CCriticalSection : public CRITICAL_SECTION
{
public:
  CCriticalSection(){ InitializeCriticalSection(this); }
  ~CCriticalSection() { DestroyCriticalSection(this); }
};
Run Code Online (Sandbox Code Playgroud)

...和另一个处理锁定和解锁......

class CSLock
{
public:
  CSLock(CRITICAL_SECTION& cs) : cs_(cs) { EnterCriticalSection(&cs_); }
  ~CSLock() { LeaveCriticalSection(&cs_); }
private: 
  CRITICAL_SECTION& cs_;
};
Run Code Online (Sandbox Code Playgroud)

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


dar*_*mos 1

我决定坚持 KISS 原则彻夜摇滚简化事情。我想我应该用CSharedMemoryClassastd::tr1::shared_ptr<BYTE>和 a替换 a CCriticalSection,这样可以保护它免受并发访问。恕我直言,两者都是CImagenow 的成员,现在可以更好地分开关注点。

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