Возникновение тупиковой ситуации с многопоточным приложением ведения журнала.
Небольшой фон:
В моем основном приложении работает 4-6 потоков. Основной поток, отвечающий за мониторинг работоспособности различных вещей, которые я делаю, обновление графического интерфейса и т. д. Затем у меня есть поток передачи и поток приема. Потоки передачи и приема взаимодействуют с физическим оборудованием. Иногда мне нужно отлаживать данные, которые видят потоки передачи и приема; то есть печатать на консоль, не прерывая их из-за критического характера данных. Данные, кстати, идут по шине USB.
Из-за многопоточного характера приложения я хочу создать консоль отладки, на которую я могу отправлять сообщения из других моих потоков. Консула отладки работает как поток с низким приоритетом и реализует кольцевой буфер, так что при выводе на консоль отладки сообщение быстро сохраняется в кольцевом буфере и наборах и событиях. Поток консоли отладки получает события WaitingOnSingleObject из входящих связанных сообщений, которые приходят. При обнаружении события поток консоли обновляет отображение графического интерфейса с сообщением. Просто, а? Вызовы печати и поток консоли используют критическую секцию для управления доступом.
ПРИМЕЧАНИЕ. Я могу настроить размер кольцевого буфера, если вижу, что удаляю сообщения (по крайней мере, такова идея).
В тестовом приложении консоль работает очень хорошо, если я медленно вызываю ее метод Print с помощью щелчков мыши. У меня есть кнопка, которую я могу нажать для отправки сообщений на консоль, и она работает. Однако, если я помещаю какую-либо нагрузку (много вызовов метода Print), все блокируется. Когда я отслеживаю взаимоблокировку, отладчик моей IDE отслеживает EnterCriticalSection и остается там.
ПРИМЕЧАНИЕ. Если я удаляю вызовы Lock/UnLock и просто использую Enter/LeaveCriticalSection (см. код), я иногда работаю, но все равно оказываюсь в ситуации тупиковой блокировки. Чтобы исключить тупиковые ситуации для стека push/pop, я прямо сейчас вызываю 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, моя мертвая блокировка (или проблема с зависанием) вернется.
Итак, после дальнейших тестов с гораздо большей нагрузкой оказалось, что мой тупик не исчез. При сверхвысокой нагрузке я продолжаю зависать или, по крайней мере, мое приложение зависает, но это почти не используется, так как я исправил проблему с кольцевым буфером. Если я удвою количество вызовов Print в MyTest2, я легко могу заблокироваться каждый раз....
Кроме того, мой обновленный код отражен выше. Я знаю, что мои вызовы событий Set & Reset должны находиться внутри вызовов критической секции.