我的程序在我可以重现的小场景中随机崩溃,但它发生在来自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
答案 0 :(得分:5)
“指定的无效句柄”返回代码描绘了一个非常清晰的图片,表明您的关键部分对象已被解除分配;当然假设它是在开始时正确分配的。
您的RAII课程似乎可能是罪魁祸首。如果您退后一步并考虑一下,您的RAII类违反了Sepration Of Concerns原则,因为它有两个工作:
我看到的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(或任何非默认包装)中。答案 2 :(得分:1)
我决定坚持KISS原则,并且摇滚所有nite 简化事情。我想我会将CSharedMemoryClass
替换为std::tr1::shared_ptr<BYTE>
和CCriticalSection
,以保护其免受并发访问。两者现在都是CImage
的成员,现在关注点更好,恕我直言。
这解决了奇怪的关键部分,但现在看来我有std::tr1::shared_ptr
引起的内存泄漏,你可能会看到我很快发布它......它永远不会结束!