[gelöst] [QT4] Zugriffsprobleme auf QList in Thread

Alles rund um die Programmierung mit Qt
Antworten
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

[gelöst] [QT4] Zugriffsprobleme auf QList in Thread

Beitrag von heikob »

Hallo,

ich schreibe an einem Programm zur Anzeige von Bildern. Dabei wird innerhalb eines Threads eine gewisse Anzahl an Bildern vorgehalten um die Zugriffe auf die Festplatte bzw. CD-ROM zu verringern. Wenn der Ablauf in die Nähe der Grenzen kommt, wird ein Teil des Speichers frei gegeben und mit neuen Bildern gefüllt.

Dies funktioniert mittlerweile auch sehr gut. Nur kommt es manchmal in der Phase, in der der Thread neue Bilder lädt und die Hauptapplikation ein Bild vom Thread anfordert, zu einem Programmabsturz wegen eines Speicherzugrifffehlers. Auch die Absicherung mit Hilfe von Mutex brachte hierbei keine Besserung.

Gibt es eine Möglichkeit, den Zugriff auf die Liste während des Ladevorgangs zu sperren? Oder wie ist das Problem sonst zu lösen?

Den Quelltext usw. findet ihr unter http://www.berberich-is.de/forum/dia.tar.gz
Falls Fragen bezüglich des Programmaufbaus bzw. zum Programmablauf auftreten, erkläre ich natürlich gerne. :lol:

Sonstige Meinungen zum Prog. und zum Programmierstil (bin zugegebener maßen noch recht unerfahren ud freue mich über neue Erkenntnisse) gerne hier oder via PM.

Vielen Dank für die Hilfe
Heiko
Zuletzt geändert von heikob am 30. September 2005 22:21, insgesamt 1-mal geändert.
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Beim Durchstönbern des Codes ist mir folgendes aufgefallen:

In main.cpp steht:

Code: Alles auswählen

    dia Dia;
    Dia.showMaximized();
    Dia.show();
Es sollte doch ein show oder showMaximized reichen, oder?

Desweiteren würde ich den Aufbau Deiner main Funktion etwa so umbauen:

Code: Alles auswählen

    QApplication * app = new QApplication( argc, argv );
    dia Dia;
    Dia.showMaximized();
    Dia.show();
    app->connect( app, SIGNAL(lastWindowClosed()), app, SLOT(quit()) );
    int execCode = app.exec();
    delete app;
    return execCode;
Der folgende Kommentar hat etwas mit Konvention zu tun, ist also nicht wirklich eine Kritik: Wenn Du den "CamelSytle" bevorzugst (was Qt übrigens von Hause aus tut), dann solltest Du statt "dia Dia;" lieber "Dia dia;" schreiben. Klassennamen fangen meist mit Großbuchstaben an. Ich rate Dir zu dieser Schreibweise, da Sie weit verbreitet ist. (Wahrscheinlich erzähl ich Dir nichts neues :) )

Du sagst, beim Anfordern eines Bildes gibt es einen Fehler. Dann könnte es doch sein, dass die Funktion "QImage imageThread::getImage(int ID)" z.B. mittels "return imageBuffer[ID-leftBufferID-1];" auf "falschen" Speicher zugreift. Zumindest würde ich an Deiner Stelle hier eine Kontrolle einbauen.

Ich habe auch mal weiter geschaut, aber ich empfehle, dass Du Dein Programm zunächst mit qDebug()-Aufrufen vollstopfst und dann den Ort ausfindig macht, an dem es einen Fehler gibt.

Viel Glück!
Ciao
ArneStocker
Beiträge: 300
Registriert: 3. November 2004 16:15
Wohnort: Berlin

Beitrag von ArneStocker »

Hi

Du musst m.e. verhindern, dass zwei threads gleichzeitig auf die gleichen bilddaten zugreifen, soll heissen der eine thread erzeugt das objekt und liest gerade ein, während der andere bereits auslesen möchte

Wenn ich es richtig verstanden haben führt der Weg dazu in QT über die QMutex Klasse. Damit kannst Du (m.E.) den Vorgang des Einlesens gegen den des Auslesens sperren (ich habe zugegebenermassen noch nicht mit der Klasse gearbeitet) aber mit anderen Klassenbiblitheken hat es funktioniert.

Wenn Du den Vorgang des ein- und auslesens nicht prozuderal sonder objektbezogen abgrenzen möchtest (soll heissen ein- und auslesen soll zwar gleichzeitig aber nicht für das gleiche objekt möglich sein), dann empfiehlt es sich, die Zeiger derjenigen objekte, die gerade gesperrt sind, temporär für die Dauer des einlesens in eine Liste (z.B. für einlesen) abzulegen und vor dem zugriff durchs auslesen diese Liste zu checken (nur die Zeiger nicht die objekte selbst !!!). Du kannst auch umgekehrt die bereits freigegebenen Objekte (die werden ja wohl nur eimal eingelesen) in eine andere Liste stecken.


Gruss Arne

PS.: die Klasse QPtrList ist z.B. thread sicher (zumnindest in Qt3.x).
PPS.: in deinem Betreff schreibst Du was von QList, die gibt es in QT 3.x nicht mehr. arbeitest Du mit QT 2.x ?
PPPS. : Ok ich hätte richtig lesen sollen, da stand was von Qt 4.0 gibt's da etwa wieder QList ?
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Hallo,

vielen Dank für die Antworten auf meine Frage. Leider haben sie noch nicht zur Behebung des Problems geführt. Allerdings gab es doch eine Reihe von interessanten und sinnvollen Anregungen.

Die Vermutung, dass der Fehler innerhalb des Threads auftritt, trifft vermutlich nicht zu. Auch die Rückgabe des Images mit

Code: Alles auswählen

return imageBuffer[ID-leftBufferID-1];
dürfte nicht die Ursache des Problems sein, da das Programm beim Absturz noch ein wenig weiter läuft und immer korrekte Positionen innerhalb des Buffers angefordert werden. Allerdings habe ich zur Sicherheit die Ladekontrolle so verändert, dass das überflüssige Bild erst entfernt wird, wenn das neue Bild geladen wurde. So bleibt der Speicher immer bei einer Größe von 16 Bildern. Den neuen Code habe ist unter http://www.berberich-is.de/forum/dia_update1.zip bereit gestellt. Darin befindet sich ebenfalls ein kleines Bild über den Ablauf. Dabei ist anzumerken, dass der zweite Zustand eingenommen wird, wenn das Bild mit der ID 13 angezeigt wird. Die grünen Felder bilden einen zusätzlichen Schutzmechanismus, weshalb ich auch nicht glaube, dass das Problem am Thread liegt. Jetzt bleibt mir noch die Steuerung in dia.ui.h näher unter die Lupe zu nehmen. Das dumme an diesem Problem ist, dass es zwar immer wieder auftritt, aber immer an anderen Positionen. Aus diesem Grund bin ich selbst für Vermutungen dankbar.

@kowi1134:
Danke für die allgemeinen Hinweise zu dem Programm. Ab dem nächsten Projekt werde ich mich auch an die Namenskonventionen halten.

Viele Grüße
Heiko
ArneStocker
Beiträge: 300
Registriert: 3. November 2004 16:15
Wohnort: Berlin

Beitrag von ArneStocker »

ich habe mir Deinen code mal angesehen, so kann das nicht funktionieren. Du rufst QMutex nur einmal mit einem mutex.lock() und anschliessendem mutex.unlock in der Methode imageThread::run() auf. In imageThread::run() fügst Du Deine Bilder dem ImageBuffer zu und liest sie (wahrscheinlich) parallel dazu im Haupthread mit imageThread::getImage() aus.

Da die Methode imageThread::getImage() vom Haupthread aufgerufne wird, musst Du sie gegen die Methode imageThread::run() sperren. Das geht wie folgt :

Code: Alles auswählen

QImage imageThread::getImage(int ID)
{
	QImage retImage;
	mutex.lock();						// gegen imageThread::run locken
	std::cout<<"getImage("<<ID<<")"<<std::endl;
	
	int positionInBuffer=ID-leftBufferID-1;
	std::cout<<"positionInBuffer="<<positionInBuffer<<std::endl;
	
	.. bla 

	retImage = imageBuffer[positionInBuffer];  // auslesen des Buffers vor unlock
	mutex.unlock();                                          // lock freigeben
	return retImage;                                         // return nach unlock
}
Ob das der einzige Problembereich ist, habe ich nicht geprüft. Das Rumgefummle mit QImage retImage ist notwendig, weil Du den imageBuffer erst (threadsicher) auslesen und anschliessen den lock freigeben musst. Du kannst es jedoch mit der Klasse QMutexLocker vermeiden (siehe Doku).

Gruss Arne
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Hallo ArneStocker,

vielen Dank für deinen Hinweis. Damit konnte ich das Problem unter Linux lösen. Seitdem habe ich es nicht mehr geschafft das Programm unter Linux zum Absturz zu bringen.

Leider hat es allerdings den Anschein, als ob die Verriegelung mit Hilfe von mutex unter Windows nicht existent wäre, da dort das Programm ähnlich oft abstürzt wie zuvor unter Linux. Muss ich unter Windows etwas noch zusätzlich beachten? Zur Sicherheit werde ich mich nochmal genauer mit der Klasse QMutex und QMutexLocker beschäftigen.

Heiko
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Mehr oder weniger zufällig habe ich mal etwas in der Bugliste von Qt gestöbert und folgendes gefunden:
80391 - Operating on different QImages in multiple threads can crash

Description:
Loading QImages in threads and using the QImage::scaled() function often crashes with a race condition.
Könnte dies die Ursache für meine Probleme sein? Ich nutze ja sowohl beim Einlesen (im Thread) sowie bei der Ausgabe (im Hauptthread) die scale()-Funktion. Ich würde gerne eure Meinung dazu wissen, bevor ich anfange die aktuellen Snapshots zu kompilieren. Dauert ja immer ein wenig. :)
Es wäre doch gelacht, wenn wir das Problem nicht noch in den Griff kriegen würden.

Heiko
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Hm, und wo ist denn Mutex in dem Hauptthread? Ich meine in setImage()..
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Hallo lepsai,

kannst du mir dies bitte näher erläutern? Vor allem wie ich das umsetzen soll? Besonders da ich nur Beispiele gefunden habe, die mit meinem Fall nicht vergleichbar sind. *grübel*
Hm, und wo ist denn Mutex in dem Hauptthread? Ich meine in setImage()..
Allerdings habe ich aus lauter Verzweiflung die komplette Ladekontrolle neu implementiert. Dabei wurde die eigentliche Kontrolle über die QList<QImage> an den Hauptthread übertragen. Ich umreiße am Besten kurz die aktuelle Funktionsweise:
1. Hauptthread stellt fest, dass er neue Bilder benötigt. und setzt die nötigen Informationen über eine Funktion im Thread und setzt eine Statusvariable.
2. Der Thread wird gestartet und liest die Bilder in eine eigene Liste QList<QImage> ein und sendet ein Signal über die Beendigung des Ladevorgangs an den GUI-Thread.
3. Der Slot im GUI-Thread kopiert nun die Bilder in die Liste im GUI-Thread. Nach Abschluß wird die Statusvariable zurückgesetzt.

Fehlerbeschreibung Linux:
Solange der Aufruf und die Anzeige von Bildern in dem Intervall, in dem die Statusvariable gesetzt ist, unterbleibt, läuft das Programm ohne Probleme. Wird innerhalb des Ladevorgangs ein Bild aufgerufen, kann es unter nicht näher zu bestimmenden Umständen zum Programmabsturz kommen. Allerding geschieht dies nur innerhalb der Phase, in der der Thread die Bilder von der Festplatte liest und nicht im Laufe des Kopiervorgangs zwischen den Threads.

Fehlerbeschreibung Windows:
Der unter Linux beschriebene Fehler tritt ebenfalls unter Windows auf. Allerdings kommt es hier auch zum Programmabsturz, wenn der Zugriff auf die Liste im GUI-Thread während des Ladevorgangs unterbleibt. Auch bei der abgesicherten manuellen Navigation kann es zum Absturz kommen. Insgesamt stürzt das Programm unter Windows bedeutend häufiger ab als unter Linux, was für mich nicht nachvollziehbar ist.

Woran liegt es, dass das Programm unter Windows (XP SP2) wesentlich häufiger zum Absturz kommt als unter Linux? Wie kann es sein, dass beim Zugriff auf unterschiedlichen Instanzen von QList Elementen das Programm zum Absturz gebracht werden kann, auch wenn QList nicht thread-safe ist? Wie ist es möglich, diesen Fehler zu beheben? Ich bin mitr meinem Latein jetzt wirklich am Ende. Ich hoffe, dass es jemand gibt, der dieses Problem kennt, und/oder mir sagen kann, welchen Fehler ich begehe, der zu diesen Problemen führt.

Quelltexte: http://www.berberich-is.de/forum/dia.zip

Vielen Dank
Heiko
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

in deinem Thread wollteste die Daten durch einen Mutex absichern, aber das funktioniert nur dann, wenn die Zugriffe in dem anderen Thread auch durch denselben Mutex geschuetzt sind... Sonst macht es einfach keinen Sinn, was du da geschrieben hast...
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Soweit dachte ich mir das schon, nur bin ich mir nicht sicher, wie ich das umsetzen soll. Kannst du mir den entsprechenden Aufruf, du hast ja den Quelltext, mitteilen? Wie würdest du das lösen?
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

ne, mutex alleine wird nicht reichen, du brauchst QApplication::postEvent() und customEvent() fuer den Datentransfer zu dem GUI-Thread. Such mal in diesem Forum nach postEvent(), ich glaube das haben wir schon diskutiert...
heikob
Beiträge: 81
Registriert: 23. März 2005 23:20

Beitrag von heikob »

Nach weiteren Versuchen hat sich herausgestellt, dass es mehrere Gründe für meine Probleme gab. Zum Einen hatte lepsai Recht damit, dass ich im Thread das gleiche Mutex-Objekt benötige wie im GUI-Thread. Allerdings habe ich das einfach über eine Übergabe des Mutex-Objektes beim Aufruf des Threads gelöst. Das ist einfacher als an dieser Stelle mit Signalen zu arbeiten.

Zum Anderen hat der weiter oben beschriebene Bug tatsächlich manchmal zugeschlagen und manchmal einen Programmabsturz verursacht hat. Leider ist es mir noch nicht gelungen, ein aktuelles Snapshot nach dem 22. Sep. unter Windows zu kompilieren, denn seitdem soll das Problem gefixt sein. Wir werden sehen. :P Auf jeden Fall hat es geholfen, die Skalierung des Bildes beim Laden heraus zu nehmen. Seitdem läuft das Programm ohne Probleme und ist nicht mehr abgestürzt.

Vielen Dank an alle, die bei der Problembehebung beteiligt waren. Es war ein langer und harter Kampf, aber wenigstens war er erfolgreich.

Heiko
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Hurra!
Antworten