一个多线程日志应用出现死锁情况。我的主应用程序有4-6个运行线程,其中主线程负责监视各种事务的健康状况、更新GUI界面等。然后我有一个发送线程和一个接收线程,发送和接收线程用于与物理硬件通信,由于数据的时序性,我有时需要调试发送和接收线程看到的数据,即在不中断它们的情况下将其打印到控制台。顺便说一下,这些数据在USB总线上。
由于应用程序的线程性质,我希望创建一个调试控制台,可以从其他线程发送消息到该控制台。调试控制台作为低优先级线程运行,并实现了环形缓冲区,当您将消息打印到调试控制台时,该消息将被快速存储到环形缓冲区并设置事件。调试控制台的线程会等待来自入站消息的SingleObject事件。当检测到事件时,控制台线程将使用该消息更新GUI显示。简单明了吧?打印调用和控制台线程使用临界区来控制访问。
注意:如果发现消息丢失,可以调整环形缓冲区大小(至少这是想法)。
在测试应用程序中,如果我通过鼠标点击缓慢地调用其Print方法,则该控制台非常有效。我有一个按钮可以按下以将消息发送到控制台,它也能正常工作。但是,如果我加入任何负载(多次调用Print方法),则一切都会死锁。当我跟踪死锁时,IDE的调试器跟踪到EnterCriticalSection并卡住了。
注意:如果我删除Lock/Unlock调用并只使用Enter/LeaveCriticalSection(请看代码),有时候会工作,但仍会陷入死锁状态。为排除对堆栈push/pops的死锁,我现在直接调用Enter/LeaveCriticalSection,但这并没有解决我的问题...出了什么问题?
这里是一个Print语句,使我能够将简单整数传递到显示控制台。
void TGDB::Print(int I)
{
//Lock();
EnterCriticalSection(&CS);
if( !SuppressOutput )
{
//swprintf( MsgRec->Msg, L"%d", I);
sprintf( MsgRec->Msg, "%d", I);
MBuffer->PutMsg(MsgRec, 1);
}
SetEvent( m_hEvent );
LeaveCriticalSection(&CS);
//UnLock();
}
// My Lock/UnLock methods
void TGDB::Lock(void)
{
EnterCriticalSection(&CS);
}
bool TGDB::TryLock(void)
{
return( TryEnterCriticalSection(&CS) );
}
void TGDB::UnLock(void)
{
LeaveCriticalSection(&CS);
}
// This is how I implemented Console's thread routines
DWORD WINAPI TGDB::ConsoleThread(PVOID pA)
{
DWORD rVal;
TGDB *g = (TGDB *)pA;
return( g->ProcessMessages() );
}
DWORD TGDB::ProcessMessages()
{
DWORD rVal;
bool brVal;
int MsgCnt;
do
{
rVal = WaitForMultipleObjects(1, &m_hEvent, true, iWaitTime);
switch(rVal)
{
case WAIT_OBJECT_0:
EnterCriticalSection(&CS);
//Lock();
if( KeepRunning )
{
Info->Caption = "Rx";
Info->Refresh();
MsgCnt = MBuffer->GetMsgCount();
for(int i=0; i<MsgCnt; i++)
{
MBuffer->GetMsg( MsgRec, 1);
Log->Lines->Add(MsgRec->Msg);
}
}
brVal = KeepRunning;
ResetEvent( m_hEvent );
LeaveCriticalSection(&CS);
//UnLock();
break;
case WAIT_TIMEOUT:
EnterCriticalSection(&CS);
//Lock();
Info->Caption = "Idle";
Info->Refresh();
brVal = KeepRunning;
ResetEvent( m_hEvent );
LeaveCriticalSection(&CS);
//UnLock();
break;
case WAIT_FAILED:
EnterCriticalSection(&CS);
//Lock();
brVal = false;
Info->Caption = "ERROR";
Info->Refresh();
aLine.sprintf("Console error: [%d]", GetLastError() );
Log->Lines->Add(aLine);
aLine = "";
LeaveCriticalSection(&CS);
//UnLock();
break;
}
}while( brVal );
return( rVal );
}
MyTest1和MyTest2只是我在按下按钮时调用的两个测试函数。无论我点击按钮的速度有多快,MyTest1都不会引起任何问题。然而,几乎每次都会出现死锁问题。
// No Dead Lock
void TTest::MyTest1()
{
if(gdb)
{
// else where: gdb = new TGDB;
gdb->Print(++I);
}
}
// Causes a Dead Lock
void TTest::MyTest2()
{
if(gdb)
{
// else where: gdb = new TGDB;
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
gdb->Print(++I);
}
}
更新: 发现了我的环形缓冲实现中的一个错误。在负载较重时,当缓冲区包装时,我没有正确检测到已满的缓冲区,因此缓冲区没有返回。我相信这个问题现在已经解决了。一旦我修复了环形缓冲区问题,性能得到了很大的提升。然而,如果我减少iWaitTime,我的死锁(或者冻结)问题就会再次出现。
因此,在进行更多的测试后,针对更重的负载,似乎我的死锁并未消失。在超级重载下,我的应用程序仍然死锁或至少冻结,但没有之前那样严重了,因为我解决了环形缓冲问题。如果我在MyTest2中增加Print调用的数量,我很容易每次都会锁定……
另外,我的更新代码已经反映在上面。我现在确保我的Set和Reset事件调用位于关键部分调用内。