多线程日志记录应用程序出现死锁情况。
小背景:
我的主应用程序有 4-6 个线程正在运行。主线程负责监视我正在做的各种事情的运行状况、更新 GUI 等......然后我有一个传输线程和一个接收线程。发送和接收线程与物理硬件通信。我有时需要调试发送和接收线程看到的数据;即打印到控制台,而不会由于数据的时间紧迫性而中断它们。顺便说一句,数据位于 USB 总线上。
由于应用程序的线程性质,我想创建一个调试控制台,可以从其他线程向其发送消息。调试控制台作为低优先级线程运行,并实现环形缓冲区,以便当您打印到调试控制台时,消息会快速存储到环形缓冲区并设置和事件。调试控制台的线程根据传入的绑定消息来处理 WaitingOnSingleObject 事件。检测到事件时,控制台线程会使用该消息更新 GUI 显示。简单吧?打印调用和控制台线程使用关键部分来控制访问。
注意:如果我发现我正在丢弃消息,我可以调整环形缓冲区大小(至少是这样)。
在测试应用程序中,如果我通过鼠标单击缓慢调用其 Print 方法,控制台工作得非常好。我有一个按钮,可以按下它来向控制台发送消息,并且它可以工作。但是,如果我施加任何类型的负载(多次调用 Print 方法),一切都会死锁。当我跟踪死锁时,我的 IDE 调试器会跟踪 EnterCriticalSection 并驻留在那里。
注意:如果我删除 Lock/UnLock 调用并仅使用 Enter/LeaveCriticalSection(请参阅代码),我有时会工作,但仍然发现自己处于死锁情况。为了排除堆栈推送/弹出的死锁,我现在直接调用 Enter/LeaveCriticalSection 但这并没有解决我的问题....这是怎么回事?
这是一个 Print 语句,它允许我将一个简单的 int 传递到显示控制台。
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 都不会引起问题。 MyTest2 几乎每次都会死锁。
// 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 调用次数加倍,我每次都很容易锁定......
另外,我更新的代码反映在上面。我知道确保我的设置和重置事件调用位于关键部分调用内。