EnterCriticalSection Deadlock
Nachdem, was zu sein scheint ein dead-lock-situation bei multi-threaded-logging-Anwendung.
Wenig hintergrund:
Meine Hauptanwendung 4-6 threads laufen. Der Haupt-thread verantwortlich für die überwachung der Gesundheit der verschiedenen Dinge, die ich Tue, Aktualisierung GUIs, etc... Dann habe ich ein sende-thread und einen Empfangs-thread. Die sende-und Empfangs-threads zu sprechen zu physischen hardware. Brauche ich manchmal, um die debug-Daten, dass die sende-und Empfangs-threads zu sehen sind; D. H. das drucken auf einer Konsole, ohne diese zu unterbrechen aufgrund Ihrer zeitkritischen Natur der Daten. Die Daten, die durch die Art und Weise, auf einer USB-bus.
Aufgrund des threading-Art der Anwendung, erstellen Sie eine debug-Konsole, dass ich Nachrichten senden kann, um aus meinen anderen threads. Die debug-beim bedienungsterminal läuft als niedrig priorisierter thread und implementiert einen Ringpuffer, so dass, wenn Sie drucken, um die debug-Konsole, die Nachricht ist schnell gespeichert, um einen ring-Puffer und sets und event. Die debug-Konsole-thread sitzt WaitingOnSingleObject Ereignisse aus der gebundenen Botschaften, die kommen. Wenn ein Ereignis erkannt wird -, Konsolen-thread-updates ein GUI-display mit der Meldung. Einfach, eh? Die Druckerei anrufen und die Konsole thread einen kritischen Abschnitt verwenden, um den Zugriff Steuern.
HINWEIS: ich einstellen kann das ring-Puffer-Größe, wenn ich sehe, dass ich mich fallenlassen Nachrichten (zumindest ist das die Idee).
In einem test-Anwendung, die Konsole funktioniert sehr gut, wenn ich rufen Sie die Print-Methode langsam über Maus-Klicks. Ich habe einen button, den ich drücken kann, zum senden von Nachrichten an die Konsole und es funktioniert. Allerdings, wenn ich jede Art von Last (viele Aufrufe von Print-Methode), alles dead-locks. Wenn ich trace die dead-lock, meine IDE-debugger Spuren zu EnterCriticalSection und sitzt dort.
HINWEIS: Wenn ich entfernen Sie die Sperren/Entsperren der Anrufe und benutzen Sie einfach die EINGABETASTE/LeaveCriticalSection (siehe code) arbeite ich manchmal, aber noch immer finde mich in einem dead-lock-situation. Um auszuschließen, deadlocks auf stack push/pops, ich nenne Sie Enter/LeaveCriticalSection direkt jetzt, aber diese nicht lösen, mein Problem.... Was ist denn hier Los?
Hier ist eine Print-Anweisung, die es erlaubt mir zu übergeben, die in eine einfache int, um die display-Konsole.
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 und MyTest2 sind nur zwei test-Funktionen, die ich als Reaktion auf einen Knopf drücken. MyTest1 nie ein problem verursacht, egal wie schnell ich auf die Schaltfläche klicke. MyTest2 dead locks fast immer.
//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:
Einen Fehler gefunden in meinem Ringpuffer-implementation. Unter der schweren Last, als Puffer gewickelt, ich wollte nicht erkennen, die der Puffer voll ist, so richtig Puffer war nicht zurück. Ich bin mir ziemlich sicher, dass Problem ist jetzt gelöst. Sobald ich der festen ring buffer Problem, Leistung viel besser. Wenn ich allerdings die Abnahme der iWaitTime, meine dead lock (oder einfrieren-Problem) gibt.
Also nach weiteren tests mit einer viel schwereren Last es scheint, dass mein deadlock ist nicht verschwunden. Unter super heavy load ich weiter deadlock oder zumindest meine app abstürzt, aber nicht wo in der Nähe, es zu verwenden, da ich der festen ring-Puffer-problem. Wenn ich die doppelte Anzahl an Print-Anrufe in MyTest2 ich einfach sperren können zu jeder Zeit....
Auch, meine aktualisierte code spiegelt sich vor. Ich weiß, dass meine Set & Reset-Ereignis-Anrufe werden innerhalb Kritischer Abschnitt ruft.
- Offtopic: ich Frage mich, warum der code nicht markieren? In der Bearbeiten-Ansicht... Gut tut es manchmal, sowieso. =/
- highlights für mich... Vielleicht versuchen Sie, clearing-browser-cache und Neustart?
- Sie können laufen in einer debugger-rechten? Wo sind die threads, wenn es "deadlocks" ?
- ja, ich kann laufen, diese in einen debugger und kann ich auch in meinem main-code-Basis, so lange ich keine Treffer mit jeder Art der Belastung; d.h. der Aufruf der TDGB::Print-Methode in eine for-Schleife oder nacheinander. Bitte Lesen Sie meine beiden test-Funktionen (MyTest1 & MyTest2). Im debugger bekomme ich die EnterCriticalSection und hängen.
- Welche IDE verwendest du in?
- Die IDE von C++ Builder, Rad XE
Du musst angemeldet sein, um einen Kommentar abzugeben.
Mit diesen Optionen oben geschlossen, ich würde nur Fragen über diese "Info" - Objekt. Ist es ein Fenster, das Fenster ist es übergeordnet an, und welcher thread wurde es erstellt?
Wenn Info, oder das übergeordnete Fenster erstellt wurde, auf den anderen thread, dann folgende situation könnte auftreten:
Die Konsole Thread in einem kritischen Abschnitt, der Verarbeitung der Nachricht.
Der Haupt-thread ruft die Print () - und-Blöcken, die auf einen kritischen Abschnitt warten für die Konsolen-Thread, um Sie zu entriegeln.
Die Konsole thread ruft eine Funktion auf Info (Caption), die Ergebnisse in das system senden eine Nachricht (WM_SETTEXT) an das Fenster. SendMessage blockiert, da der Ziel-thread nicht in eine Nachricht warnbare Zustand (nicht gesperrt auf einen Aufruf von GetMessage/WaitMessage/MsgWaitForMultipleObjects).
Nun hast du einen deadlock.
Diese Art von #$(%^ kann passieren, wenn Sie mischen blockieren Routinen mit nichts interagiert mit windows. Die nur entsprechende blocking-Funktion zu verwenden, die auf einer GUI-thread ist MSGWaitForMultipleObjects sonst SendMessage-Aufrufe auf windows gehostet wird auf das Gewinde kann leicht deadlock.
Vermeidung dieser beinhaltet zwei mögliche Ansätze:
Ohne zu wissen, wo es ist Deadlock-dieser code ist schwer zu verstehen. Zwei Kommentare tho:
Gegeben, dass diese c++, sollten Sie mit Hilfe eines Auto-Objekts führen Sie die sperren und entsperren. Nur, falls es jemals wird nicht katastrophal für die Anmeldung eine exception werfen.
Sie zurücksetzen der Veranstaltung in Reaktion auf WAIT_TIMEOUT. Dies lässt ein kleines Fenster der Gelegenheit für eine 2. Print() aufrufen, um das Ereignis festzulegen, das während der worker-thread nach der Rückkehr von WaitForMultiple, aber bevor es in den kritischen Abschnitt. Die Folge, das Ereignis wird zurückgesetzt, wenn es tatsächlich Daten angemeldet.
Aber Sie tun müssen, um zu Debuggen und herauszufinden, wo es "Deadlocks". Wenn ein thread stecken, EnterCriticalSection, dann finden wir heraus, warum. Wenn keiner der beiden Threads ist, dann werden die unvollständigen drucken ist nur das Ergebnis einer Veranstaltung verloren geht.
Ich würde stark empfehlen eine lockfree Umsetzung.
Dadurch werden nicht nur die Vermeidung potenzieller deadlock, aber debug-Instrumentierung ist ein Ort, wo Sie absolut nicht wollen, um eine Sperre. Die Auswirkungen der Formatierung, die debug-Meldungen auf timing einer multi-threaded Anwendung ist schlimm genug... mit sperren synchronisieren Sie Ihre parallelen Codes, nur weil Sie instrumentiert, es macht Debuggen zwecklos.
Was ich Vorschlage, ist eine SList-based-design (Die Win32-API bietet eine SList Implementierung, aber Sie können eine thread-sichere Vorlage leicht genug mit InterlockedCompareExchange und InterlockedExchange). Jeder thread wird ein pool von Puffern. Jeder Puffer wird die Länge der Gewinde-es kam, nach der Verarbeitung der Puffer, der Protokoll-manager poste den Puffer zurück zum Ursprung des Threads SList für die Wiederverwendung. Threads, die möchte eine Nachricht schreiben, wird die post einen Puffer, um die logger-thread. Dies verhindert auch, dass jeder thread vor dem verhungern anderen threads der Puffer. Eine Veranstaltung zu wecken, der logger-thread, wenn ein Puffer in der queue vollendet das design.
comp.programming.threads
Archive. Ich lernte eine Menge von einigen der gleichen Experten, bevor Microsoft tötete den öffentlichen MS-newsgroups.