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