Exception_Access_Violation bei Multithreadin

Übersicht BlitzMax, BlitzMax NG Allgemein

Neue Antwort erstellen

M0rgenstern

Betreff: Exception_Access_Violation bei Multithreadin

BeitragMo, Apr 08, 2013 14:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo Leute,

Blitzmax' Multithreading raubt mir grade den letzten Nerv.
Das Problem ist folgendes:
Ich habe eine Debugger Klasse, die eine Queue hat.
Die update Methode der Debugger Klasse läuft in einem eigenen Thread.
In dieser Methode wird die Queue geleert und alle in der Queue enthaltenen Debugnachrichten werden in die Konsole geschrieben und/oder in eine Datei.
Dann kann man von außen, also vor allem aus einem anderen Thread, neue Nachrichten in die Queue einfügen.

Dazu habe ich ein kleines Testprogramm geschrieben, das einfach mal 100 (wahlweise mehr oder weniger) Nachrichten in die Queue steckt und den Debugger das ganze abarbeiten lässt.
Dabei funktioniert das ganze so: Sobald eine Nachricht in die Queue gesteckt wird, fängt der Debugger auch an zu arbeiten.
Also, das Hinzufügen von Nachrichten und das Abarbeiten funktioniert nicht nacheinander, sondern "gleichzeitig", natürlich abhängig von den verwendeten Mutexen.

Mein Problem ist jetzt: In 90% der Fälle bekomme ich (im Debugmodus) einen "EXCEPTION_ACCESS_VIOLATION"-Fehler. Jedoch wird weder ein Callstack noch eine Zeile angezeigt.
Meist passiert das, nachdem so ungefähr 10 Nachrichten geschrieben wurden. Manchmal auch mehr und manchmal läuft das Programm auch bis zum Ende durch.

Wodurch wird so ein Fehler normalerweise ausgelöst?
Multithreaded Programme sind leider ziemlich schlecht zu debuggen, da die Debugausgaben keine feste Reihenfolge haben, wenn sie von mehreren Threads kommen.
Und Debugstop bringt nicht allzuviel, da das Problem dann nicht auftritt.

Zur Sicherheit poste ich auch mal den Code.
Nachricht einfügen:
BlitzMax: [AUSKLAPPEN]
Function enqueueMessage(message_To_Enqueue:TMessage)
'Auf Null testen.
If(message_To_Enqueue = Null) Then
Throw New TNullpointer_Exception.NewException("'message_To_Enqueue' is NULL.", "TDebugmanager.bmx", ..
"TDebugmanager", "enqueueMessage")
EndIf

'Mutex locken.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Message in die Queue einfuegen.
TDebugmanager.getSingleton_Instance().message_Queue.enqueue(message_To_Enqueue)

'Mutex freigeben
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Signal an den update thread geben.
SignalCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable)
End Function


Debugger updaten:
BlitzMax: [AUSKLAPPEN]
Function _update:Object(thread_Object:Object)
'Endlosschleife (bis dem Manager gesagt wird er soll sich abschalten.)
Repeat
'Arbeite nur, wenn die Schlange nicht leer ist.
If(Not(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty())) Then
'Mutex locken.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Alle Nachrichten aus der Schlange nehmen.
While(Not(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty()))
Local message_To_Process:TMessage = TMessage(TDebugmanager.getSingleton_Instance().message_Queue.dequeue())
TDebugmanager.getSingleton_Instance()._processMessage(message_To_Process)
Wend

'Mutex unlocken.
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

Else
'Nichts zu tun? Dann warte auf ein Signal.
WaitCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable, ..
TDebugmanager.getSingleton_Instance().queue_Mutex)
EndIf
Until TDebugmanager.isFinished()
End Function


Wäre wirklich super, wenn mir jemand nen Tipp geben könnte, woran sowas liegen kann.
Oft ist es auch so: Wenn ich viele Debugausgaben dazwischen habe, dann läuft es häufiger durch ohne Fehler zu produzieren.

Lg,
M0rgenstern

DAK

BeitragMo, Apr 08, 2013 15:05
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich bin mir nicht sicher, aber aus der Befehlsreferenz entnehme ich, dass LockMutex den Mutex sperrt, aber wenn der Mutex schon versperrt ist, ihn einfach ignoriert und an ihm vorbeimarschiert. Probier mal, ob ein

BlitzMax: [AUSKLAPPEN]

While(Not TryLockMutex())
Wend


das Problem löst.

Ich bin grad nicht an nem Computer, deswegen kann ich selbst es nicht testen.
Gewinner der 6. und der 68. BlitzCodeCompo

M0rgenstern

BeitragMo, Apr 08, 2013 15:56
Antworten mit Zitat
Benutzer-Profile anzeigen
Hey.

Ich kann dir sagen, dass das nicht funktioniert, weil ich es eigentlich genauso geschrieben hatte.
Das Lockmutex ist jetzt drin, nachdem ich nach Möglichkeiten gesucht habe um den Fehler zu beheben.
Also kurz: Weder noch behebt den Fehler.
Aber vielen Dank, wäre ja immerhin möglich^^

(Nebenbei: Ich glaube, ich habe auf der Blitzmax Wiki Seite gelesen, dass Lockmutex den Thread an der Stelle anhält, bis der Mutex frei ist. Käme dann aufs selbe hinaus wie die While Schleife).

Lg,
M0rgenstern

BtbN

BeitragMo, Apr 08, 2013 17:31
Antworten mit Zitat
Benutzer-Profile anzeigen
Du benutzt die Cond-Var falsch.
Man ruft CondWait auf, während der Mutex, der an die Cond-Var gegeben wird, gesperrt ist.
Die CondVar entsperrt ihn dann, und wartet darauf, signalisiert zu werden.

Das Signalisieren muss erfolgen, während der mutex der CondVar gesperrt ist, und danach darf man ihn erst unlocken.

M0rgenstern

BeitragMo, Apr 08, 2013 19:10
Antworten mit Zitat
Benutzer-Profile anzeigen
Hey,

@BtbN:
Nach deinem Post und Recherchen im Internet habe ich jetzt den Code ein wenig abgewandelt, soweit ich das was ich gesehen habe und was du vorgeschlagen hast, verstanden habe.

Ergebnis: Die Stellen, an denen der Fehler auftritt sind viel unterschiedlicher. Mal schon nach 6 Nachrichten, mal erst nach 80 und durchlaufen tut es gar nicht mehr.
Statdessen steckt es, wenn kein Fehler kommt, nach Nachricht 99 fest und nichts passiert mehr.

Die Methoden sehen jetzt folgendermaßen aus:
Nachricht einfügen:
BlitzMax: [AUSKLAPPEN]
Function enqueueMessage(message_To_Enqueue:TMessage)
'Auf Null testen.
If(message_To_Enqueue = Null) Then
Throw New TNullpointer_Exception.NewException("'message_To_Enqueue' is NULL.", "TDebugmanager.bmx", ..
"TDebugmanager", "enqueueMessage")
EndIf

'Mutex locken.
While(Not(TryLockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)))
Wend

'Message in die Queue einfuegen.
TDebugmanager.getSingleton_Instance().message_Queue.enqueue(message_To_Enqueue)

'Signal an den update thread geben.
SignalCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable)

'Mutex freigeben
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
End Function


Liste abarbeiten:
BlitzMax: [AUSKLAPPEN]
Function _update:Object(thread_Object:Object)
'Endlosschleife (bis dem Manager gesagt wird er soll sich abschalten.)
Repeat
'Arbeite nur, wenn die Schlange nicht leer ist.
If(Not(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty())) Then

'Mutex locken.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Auf Signal warten
WaitCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable, ..
TDebugmanager.getSingleton_Instance().queue_Mutex)

'Alle Nachrichten aus der Schlange nehmen.
While(Not(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty()))
Local message_To_Process:TMessage = TMessage(TDebugmanager.getSingleton_Instance().message_Queue.dequeue())
TDebugmanager.getSingleton_Instance()._processMessage(message_To_Process)
Wend

'Mutex unlocken.
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
EndIf
Until TDebugmanager.isFinished()
End Function


Wie gesagt, ich habe es so geschrieben, wie ich es zum Teil im Internet gesehen habe und wie ich deinen Post verstanden habe.
Wobei im Internet auch relativ unterschiedliche Arten zu finden waren (Mutex unlocken bevor das Signal gegeben wird etc.).
Habe auch verschiedene Arten ausprobiert, aber keine hat den Fehler behoben. Manche haben dazu geführt, dass das Programm einfach nichts getan hat.

Lg,
M0rgenstern

BtbN

BeitragMo, Apr 08, 2013 19:19
Antworten mit Zitat
Benutzer-Profile anzeigen
"While(Not(TryLockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)))"

Wozu soll das gut sein, außer zum massiven verschwenden von CPU zeit?


Der normale ablauf von dieser Situation wäre:

Sender Seite:

Code: [AUSKLAPPEN]

MutexLock()
EnqueueMessage()
CondSignal()
UnlockMutex()



Empfänger Seite:

Code: [AUSKLAPPEN]

While Not Stop
    MutexLock()
    If(QueueEmpty())
        CondWait(Mutex)
    If(Not QueueEmpty())
        currentMessage = GetMsgFromQueue()
    MutexUnlock()
    ProcessMessage(currentMessage)
Wend


So werden locks minimiert(Kein lock während dem verarbeiten) und du verschwendest nicht CPU zeit mit busy-waiting.

M0rgenstern

BeitragMo, Apr 08, 2013 20:10
Antworten mit Zitat
Benutzer-Profile anzeigen
Hey,

Ich habe wie gesagt verschiedene Beispiele aus dem Internet ausprobiert.
Das letzte war dann eins das mit der While Schleife gearbeitet hat.

Ich habe es jetzt aber genauso geschrieben, wie du vorgeschlagen hast.
Der Fehler taucht jedoch immernoch auf.

Unter welchen Umständen bekommt man denn eine solche Fehlermeldung?
Ich hätte noch ein paar Kandidaten: Streams und Arrays.

Der Debugmanager schreibt nämlich jede Nachricht in eine Datei (in ProcessMessage).
Kann da dieser Fehler auftreten?

Die Queue, die er benutzt basiert auf Stacks, welche wiederrum auf Arrays basieren.
Wenn etwas in die Schlange eingefügt wird, so wird das einfach auf den Input-Stack der Schlange gelegt.
Dieser wird vorher bei Bedarf vergrößert.
Wenn etwas aus der Schlange entfernt wird, so wird das oberste Element vom Output-Stack der Schlange genommen. Ist der Stack leer, dann wird der Input-Stack umgekehrt auf den Output-Stack gelegt.
Ein Stack ist im Grunde nur ein Array und ein Zeiger, auf die nächste Leere Position. Wenn der Zeiger aus dem Array läuft, dann wird es einfach in der Größe verdoppelt.

Kann es sein, dass bei den Streams oder der Queue wegen dem Multithreading ein Problem auftritt?
Ich denke eigentlich, dass das nicht sein dürfte, da ja extra ein Mutex benutzt wird.

Hier ist nochmal der veränderte Code:

Nachricht einfügen:
BlitzMax: [AUSKLAPPEN]
Function enqueueMessage(message_To_Enqueue:TMessage)
'Auf Null testen.
If(message_To_Enqueue = Null) Then
Throw New TNullpointer_Exception.NewException("'message_To_Enqueue' is NULL.", "TDebugmanager.bmx", ..
"TDebugmanager", "enqueueMessage")
EndIf

'Mutex locken.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Message in die Queue einfuegen.
TDebugmanager.getSingleton_Instance().message_Queue.enqueue(message_To_Enqueue)

'Signal an den update thread geben.
SignalCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable)

'Mutex freigeben
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
End Function


Nachrichten verarbeiten:
BlitzMax: [AUSKLAPPEN]
Function _update:Object(thread_Object:Object)
'Endlosschleife (bis dem Manager gesagt wird er soll sich abschalten.)
Repeat
Local message_To_Process:TMessage
'Mutex locken.
LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

'Wenn die Schlange leer ist, dann warte.
If(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty()) Then
'Auf Signal warten
WaitCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable, ..
TDebugmanager.getSingleton_Instance().queue_Mutex)
Else
'Ansonsten nimm die naechste Message.
message_To_Process = TMessage(TDebugmanager.getSingleton_Instance().message_Queue.dequeue())
EndIf

'Mutex unlocken.
UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
TDebugmanager.getSingleton_Instance()._processMessage(message_To_Process)
Until TDebugmanager.isFinished()
End Function


Lg,
M0rgenstern

EDIT:
Also, es ist ganz komisch.
Auf meinem Rechner zuhause tritt dieses Problem nicht auf.
Ich war die letzten Tage nicht zuhause und konnte deswegen nur auf dem Laptop arbeiten.
Wie gesagt: Auf dem Desktop-PC gehts und auf dem Laptop nicht.
Wie kann denn sowas denn sein?

Lg,
M0rgenstern

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group