VC++ 2010:奇怪的关键部分错误
Posted
技术标签:
【中文标题】VC++ 2010:奇怪的关键部分错误【英文标题】:VC++ 2010: Weird Critical Section error 【发布时间】:2011-08-19 15:18:58 【问题描述】:我的程序在一个我可以重现的小场景中随机崩溃,但它发生在来自 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
【问题讨论】:
这两个类本身看起来不错。你能展示一些与你如何使用它们有关的代码吗?您确定在使用之前正确调用了构造函数(构造函数上没有线程争用吗?)。它们是否被动态分配(出于某种原因)? 您的课程与 CRT 代码没有任何关系,它使用的是 Windows。调试线程竞争和堆损坏从来都不是一件有趣的事情,祝你好运。 @Chad:我编辑了我的问题。我发布了在崩溃的线程中使用 CCriticalSections 的方式 我在看到您的编辑之前发布了我的回复。 关于您的编辑,我想到了一点。关键部分和一般同步用于保护数据,而不是代码。您的m_csAccessUse
对象对我来说似乎是多余的。
【参考方案1】:
“指定的无效句柄”返回代码清楚地描绘了您的临界区对象已被释放;当然,假设它一开始就被正确分配了。
您的 RAII 课程似乎是罪魁祸首。如果你退后一步想一想,你的 RAII 类违反了Sepration Of Concerns 原则,因为它有两个工作:
-
它为 CRITICAL_SECTION 提供分配/销毁语义
它为 CRITICAL_SECTION 提供获取/释放语义
我见过的大多数 CS 包装器的实现都以同样的方式违反了 SoC 原则,但这可能是有问题的。特别是当您必须开始传递类的实例以获取获取/释放功能时。考虑一个简单的伪代码示例:
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
,但现在没有人真正“拥有”关键部分,并且您已经放弃了对区域的一点控制,以便在另一个区域获得一点收益。
这个问题的真正“解决方案”是分离关注点。有一个用于分配和释放的类:
class CCriticalSection : public CRITICAL_SECTION
public:
CCriticalSection() InitializeCriticalSection(this);
~CCriticalSection() DestroyCriticalSection(this);
;
...和另一个处理锁定和解锁...
class CSLock
public:
CSLock(CRITICAL_SECTION& cs) : cs_(cs) EnterCriticalSection(&cs_);
~CSLock() LeaveCriticalSection(&cs_);
private:
CRITICAL_SECTION& cs_;
;
现在您可以传递原始指针或对单个 CCriticalSection 对象(可能是 const)的引用,并让工作线程在其上实例化自己的 CSLock。 CSLock 由创建它的线程拥有,这是应该的,但 CCriticalSection 的所有权显然由某个控制线程保留;也是一件好事。
【讨论】:
我看不出这与我之前所做的有什么不同,除了它使用继承而不是组合并在等效于我的 CGuard 类中调用 EnterCriticalSection 和 LeaveCriticalSection。我这样做有什么好处? 你把你的问题改成了现在我们不知道 CCriticalSection 是什么样子,CGuard 是什么,或者两者之间的关系。你说“每个 CImage 对象都包含一个 CCriticalSection 对象,它通过 CGuard RAII 锁使用它”,但除非我们看到它们是如何声明和实现的,否则我们并不真正知道这意味着什么。 抱歉,我删除了该代码,因为有人告诉我我的课程还可以,以避免使问题变得混乱。我在 Ideone.com 上发布了它,请参阅我更新的问题中的链接【参考方案2】: 确保关键部分对象不在#pragma
包装 1(或任何非默认包装)中。
确保没有其他线程(或同一个线程)正在破坏 CS 对象。运行一些静态分析工具来检查任何缓冲区溢出问题。
如果您有运行时分析工具,请运行它来查找问题。
【讨论】:
【参考方案3】:我决定坚持 KISS 原则,摇滚乐 简化事情。我想我会用std::tr1::shared_ptr<BYTE>
和CCriticalSection
替换CSharedMemoryClass
,以保护它免受并发访问。两者现在都是CImage
的成员,现在更好地分离问题,恕我直言。
这解决了奇怪的关键部分,但现在看来我有一个由 std::tr1::shared_ptr
引起的内存泄漏,你可能很快就会看到我发布它......它永远不会结束!
【讨论】:
以上是关于VC++ 2010:奇怪的关键部分错误的主要内容,如果未能解决你的问题,请参考以下文章