Seite 1 von 1

Threads: Objekte erzeuegen und löschen. Bsp. aus Qt Buch.

Verfasst: 10. November 2009 22:21
von oesterreich
Hallo Forumgemeinde,
ich habe Fragen zur Erzeugung und Löschung von Objekten im Zusammenhang mit Threads.
Im Buch "C++ Gui Programmierung mit QT4"(http://www.addison-wesley.de/9783827327291.html) habe ich ein Beispiel gefunden, das genau mein Problem behandelt. (Kapitel 4.13 Kommunikation mit hauptthread S.437 )
Das Beispiel heißt "imagepro", und kann auf der o.g seite heruntergeladen werden.
Kurz zusammengefasst: es ist eine Bildberabeitungsanwendung.
Es gibt einen Mainthread, der alle Dialoge, Menüs u.s.w erstellt. Dann gibt es einen Thread, dass die Bildmanipulationen durchfuhrt. Der Maintthread kommuniziert mit dem Unterthread indem er in eine Warteschlange des Unterthreads (Queue) Befehle (Transaktionen) ablegt.
imagewindow.cpp:

Code: Alles auswählen

...
void ImageWindow::flipHorizontally()
{
    addTransaction(new FlipTransaction(Qt::Horizontal));
}

void ImageWindow::addTransaction(Transaction *transact)
{
    thread.addTransaction(transact);
    openAction->setEnabled(false);
    saveAction->setEnabled(false);
    saveAsAction->setEnabled(false);
}
...
Der Unterthread wird aufgeweckt. Nach der Abarbeitung aller Auftrage geht er wieder ins wait.
Transactionthread.cpp:

Code: Alles auswählen

...
void TransactionThread::addTransaction(Transaction *transact)
{
    QMutexLocker locker(&mutex);
    transactions.enqueue(transact);
    transactionAdded.wakeOne();
}

void TransactionThread::run()
{
    Transaction *transact = 0;
    QImage oldImage;

    forever {
        {
            QMutexLocker locker(&mutex);

            if (transactions.isEmpty())
                transactionAdded.wait(&mutex);
            transact = transactions.dequeue();
            if (transact == EndTransaction)
                break;

            oldImage = currentImage;
        }

        emit transactionStarted(transact->message());
        QImage newImage = transact->apply(oldImage);
        delete transact;

        {
            QMutexLocker locker(&mutex);
            currentImage = newImage;
            if (transactions.isEmpty())
                emit allTransactionsDone();
        }
    }
}
...

Jetzt komt's: Die Befehle(= Transaktions) sind eigentlich Objekte. Wenn der User eine Aktion durchgefuhrt haben möchte, dann erzeugt der Mainthread ein neues Transaction Objekt. Dann fügt er einen Pointer auf soeben erstelletes Objekt in die Warteschlange des Threads (alles schön mit mutexen gesichert).
Und der Thread löscht dann in seiner Run Methode die Transaktion mit
"delete pointeraufrausgehohlteTransaktion", sobald sie ausgeführt wurde.

Also zusammen gefasst: Mainthread erzeugt Objekte, Unterthread löscht sie. (Stimmt das überhaupt?) Dann nächste Frage:
Im gleichen Buch auf S.445 steht Fett markieret: "QObject-Instanzen mussen in demselben Thread gelöscht werden, in dem sie erstellt wurden."
Da stimmt was nicht, (oder ich habe was falsch verstanden.)
Könnte mir bitte jemand helfen?

P.S. Beispiel funktioniert ohne Fehlemeldungen.

Vielen Dank.

Verfasst: 10. November 2009 23:28
von solarix
Also zusammen gefasst: Mainthread erzeugt Objekte, Unterthread löscht sie. (Stimmt das überhaupt?)
Ja.. Der eine ist der "Producer".. der andere der "Cosumer".. soweit so gut..
Im gleichen Buch auf S.445 steht Fett markieret: "QObject-Instanzen mussen in demselben Thread gelöscht werden, in dem sie erstellt wurden."
Ja, das ist etwas knifflig.. direkt löschen könnte man ein QObject vermutlich nur, wenn es vorher in den Thread (moveToThread) verschoben worden wäre.. sicher ist jedoch "deleteLater()", welche auch von einem anderen Thread aufgerufen werden kann.
Da stimmt was nicht, (oder ich habe was falsch verstanden.)
Nichts von beiden, du hast nur falsch gelesen:

Code: Alles auswählen

class Transaction
{
public:
Das ist eine normale C++-Klasse, kein "QObject". Bei QObjects ist das Eventhandling beim Löschen knifflig, aber bei dieser "Transaction"-Klasse ist das ja kein Problem.

hth..

Verfasst: 11. November 2009 09:24
von RHBaum
"QObject-Instanzen mussen in demselben Thread gelöscht werden, in dem sie erstellt wurden."
Da stimmt was nicht, (oder ich habe was falsch verstanden.)
doch das stimmt so !

Das teuflische daran ist nur, es kann funktionieren, muss aber nicht.

dreh und angelpunkt ist der new operator und das zugehoerige delete bei. (klar, auf stack erzeugte instanzen loeschst eh immer im selben thread, sei denn du kriegst nen threadwechsel mitten in nem Anweisungsblock hin ^^) Es ist nicht vorgeschreiben, wie die implementiert sind ... einige implementationen verwenden zur verwaltung des speichers informationen ueber den thread/prozess und kommen durcheinander wenn nen anderer thread das delete aufruft. dieses verhalten ist im c++ Standard ned spezifiziert, deshalb vermeiden ! ist zumindest mein kenntnissstand.

In deinem Fall ....

1. deine Transaktions ... wie komplex sind den die, was kostet dich eine kopie ? (wieviel bytes muessen kopiert werden, haben die selber noch dyn speicher allokiert ? ) wenn trivial, dann ned mit zeiger, sondern mit referenzen und kopien arbeiten. In der QT kannst auch ne kopierrichtlienie fuer eigene Objecte einbauen (das MetaClass zeugs) damit erzeugen auch deine assynchronen signale slots (queued connections) richtig kopien in dem anderen thread. und du brauchst dich um dieses problem ueberhaupt ned mehr zu kuemmern.

2. die delegierst das delete wieder in den anderen thread zurueck ....
statt die dinger im workerthread zu loeschen, richte ne loeschqueue ein, spuele die da rein, und lass den mainthread die arbeit machen ....

Ciao ...

Danke für eure Antworten.

Verfasst: 14. November 2009 20:16
von oesterreich
Danke für eure sehr hilfreiche Antworten!
Nichts von beiden, du hast nur falsch gelesen:

Code:
class Transaction
{
public:

Das ist eine normale C++-Klasse, kein "QObject". Bei QObjects ist das Eventhandling beim Löschen knifflig, aber bei dieser "Transaction"-Klasse ist das ja kein Problem.

hth..
Oh, ja. Du hast Recht. Tut mir leid.
D.h. wenn ich einen "normalen" C++ Objekt erzeuge, dann kann ich es also so, wie im Beispiel machen: Im Producer Thread erzeugte Objekte später im Konsumer Thread löschen? Kann man überhaupd deleteLater() auf "nicht Qt " Ojekte(d.h nicht auf Qobject) anwenden?
In deinem Fall ....

1. deine Transaktions ... wie komplex sind den die, was kostet dich eine kopie ? (wieviel bytes muessen kopiert werden, haben die selber noch dyn speicher allokiert ? ) wenn trivial, dann ned mit zeiger, sondern mit referenzen und kopien arbeiten. In der QT kannst auch ne kopierrichtlienie fuer eigene Objecte einbauen (das MetaClass zeugs) damit erzeugen auch deine assynchronen signale slots (queued connections) richtig kopien in dem anderen thread. und du brauchst dich um dieses problem ueberhaupt ned mehr zu kuemmern.
Mein Fall sieht so aus: In einer GUI (Mainthread: PProducer) kann der User in verschiedenen Eingabmasken etwas reinschreiben, in die andere Masken kann er eine Textdadei mit immer feter , konstanter Länge laden. Und bei jeder Maske gibt es einen Send Button.
Die Daten werden also aus GUI entgegengenommen und an das Sender Thread(Kunsumer) übergegeben. Der Sender Thread schieckt alle Aufträge stück für stück über Serielle Schnittstelle mit einem Protokol aus.
1. deine Transaktions ... wie komplex sind den die,
Meine "Transaktions" sind also die Messages von verschiedenn Arten der Eingabemasken.
Ich hätte das mir so vorgestellt: Ich mache eine Abstrakte Klassse "Message" Davon leite ich für jeden Typ der Eingabemaske je eine Spezielle Klasse ab.
Die verschiedenen Messages Unterscheiden sich von einander in der Größe der Daten, die zum Senderthread geschieckt werden: Mal ist es ein integer, mal ein Bool Wert mal ein QString (immer mit feser, vorher definierter Länge) Ausserdem Habe Messanges deren Tyt, das als Teil der Nutzinformation mit gesendet wird. Also ein Objekt soll es schon sein, nicht einfache primitive Typen.
was kostet dich eine kopie
Die grössten Messages wären die, die QString als Daten haben, ungefähr 521 Byte groß. Die Meisten aber 10 byte groß.
Ich weiss nicht in wiefern es schlimm den halben KByte zu kopieren.
[/quote] In der QT kannst auch ne kopierrichtlienie fuer eigene Objecte einbauen (das MetaClass zeugs)
So ungefähr hatte ich das bis jetzt realisiert. Für meine Objekte habe ich einen Kopier und zuweisungsoperator gemacht, und dann denn in der QQueue gespeichert gehabt. Alos keine Signale geschieckt. Jedesmall wenn der Sender die Mesages aus seiner Queue ausliest löscht er die , bis jetzt, mit delet.
Dann hatte ich die Stelle im Buch gelesen(wie es jetzt herausgestellt habe, zu schlecht gelesen) und hier gepostet.
2. die delegierst das delete wieder in den anderen thread zurueck ....
Das ist auch eine gute Idee, da ich vom Sender an GUI mitteilen möchte, ob das Senden erfplgreich war. Könnte so einen Rückkanal verwirklichen.
Das wollte ich eigentlich mit signalen erledigen.
Wie könnte ich veranlassen, dass mein Mainthread die Lösch queue abarbeitet. Da gibt es ja keine run Methode..
Was würdet Ihr dazu sagen. Ist es machbar, so wie ich es vorstelle?
Danke vielmals.

Verfasst: 16. November 2009 11:49
von RHBaum
Wie könnte ich veranlassen, dass mein Mainthread die Lösch queue abarbeitet. Da gibt es ja keine run Methode..
Noe, Dein Mainthread haengt ja in der Eventloop. Um den da rauszubekommen, gibts nur eine Möglichkeit: ein Event !

also schreib nen eventhandler, der dir die loeschqueue bereinigt ....
hat dein workerthread die daten verarbeitet und braucht die nichtmehr, also in die loeschqueue verschoben, schickt er ein Signal (event) asynchron an den mainthread, das er jetzt was machen(putzen) muss.
ned vergessen, die queue selber zu schuetzen (critical section) ned das dein workerthraed neue objecte loeschen (ergo in die loeschqueue reinschreiben) will, waehrend dein mainthraed grad beim putzen (loeschen aus der loeschqueue) ist .... !

Anmerkung: queued connections(Signale/slots ueber threadgrenzen) werden eh durch Events implementiert. Da dein empfaenger (Mainthread) eh in ner eventloop haengt, kannst also simpel und einfach Signale/Slots verwenden in dem Fall in diese Richtung.
Meine "Transaktions" sind also die Messages von verschiedenn Arten der Eingabemasken.
Also mit dynamischen anteil und selber noch mal virtuell ... würd ich das kopieren meiden wollen. der overhaed wird zu gross dann.
Dann lieber die methode mit der loeschqueue ....

Ciao ...

Verfasst: 16. November 2009 12:13
von AuE
Wäre hier nicht evtl der Einsatz von Smartpointern/autoptr hilfreich?
Die löschen sich ja "selbst" wenn keine Referenzen mehr sie benutzen.

Verfasst: 16. November 2009 12:22
von RHBaum
Die löschen sich ja "selbst" wenn keine Referenzen mehr sie benutzen.
Ja, aber machen kein threadwechsel dabei ....

bei nem refcounted pointer loescht immer der gleich, der die 1 auf die 0 decrementiert. und wenn das sein workerthread ist, ruft sein workerthread das delete auf, und er ist wieder beim problem am anfang.

weiss ned ob es smartpointer gibt, die sowas aufn richtigen thread zurueckmappen, glaub aber ned das es sowas gibt ^^

Ciao ...

Verfasst: 16. November 2009 12:48
von solarix
@RHBaum: Aus reiner Neugier: welche von Qt unterstuetzte Plattformen(OS/libc...) haben kein "thread-safe" Heap?

@oesterreich:
oesterreich hat geschrieben: D.h. wenn ich einen "normalen" C++ Objekt erzeuge, dann kann ich es also so, wie im Beispiel machen: Im Producer Thread erzeugte Objekte später im Konsumer Thread löschen?
Grundsätzlich: ja. Ich würde sagen, dass alle von Qt unterstützten Plattformen damit (mit deinem Beispiel aus dem Buch) kein Problem haben. Bei einer grösseren Applikation könnte das jedoch zu einem Problem werden ((Thread-)Code in einer Windows-DLL oder nicht-threadsafe libc auf einer exotischen Plattform) und bedarf eine besseren Strategie (welche ja hier ausführlich diskutiert werden).
oesterreich hat geschrieben:Kann man überhaupd deleteLater() auf "nicht Qt " Ojekte(d.h nicht auf Qobject) anwenden?
Nein.. deleteLater() ist eine Methode der Klasse "QObject", kein C++-Operator..

Verfasst: 16. November 2009 13:45
von RHBaum
Aus reiner Neugier: welche von Qt unterstuetzte Plattformen(OS/libc...) haben kein "thread-safe" Heap?
thread-safe iss mit sicherheit jeder halbwegs aktueller compiler....
Aber hat das was mit dem Problem zu tun ?
threadsafe bedeutet doch in dem zusammenhang nur, dass du "quasi" gleichzeitig operationen des Heaps (anlegen / freigeben von speicher) von mehreren threads aus machen kannst, ohne dass deine zentrale Heapverwaltung durcheinanderbringst. also thread-safe heisst, du musst keine globale critical section fuer deine heapzugriffe anlegen. Bei einigen aelteren c compileren mit rudimentaerer multithread und heap - unterstuetzung auf kleineren plattformen war das doch noch so ...

Beim MS Compiler mit bestimmten compilereistellungen isses definitiv noch nen problem, wenn new und delete ausm unterschiedlichen thread kommen. Das hat weniger was mit threadsicherheit zu tun, sondern eher mit der Verwaltung an sich, die infos zum thread mit nutzt.

Ciao ...

Verfasst: 16. November 2009 14:33
von solarix
RHBaum hat geschrieben:Beim MS Compiler mit bestimmten compilereistellungen isses definitiv noch nen problem, wenn new und delete ausm unterschiedlichen thread kommen. Das hat weniger was mit threadsicherheit zu tun, sondern eher mit der Verwaltung an sich, die infos zum thread mit nutzt.
Ciao ...
Thx.. das wusste ich bisher nicht.. (dass bei MS-Compiler noch irgendwelche Thread-abhaengigen Metadaten mitgefuehrt werden). Ich kannte bisher lediglich das DLL-Problem.. kennst du evt. ein Link mit weiteren Infos (ich finde lediglich die threadsafe- und DLL-Diskussionen..)?

Verfasst: 16. November 2009 15:09
von RHBaum
(dass bei MS-Compiler noch irgendwelche Thread-abhaengigen Metadaten mitgefuehrt werden).
In den Standardeinstellungen läuft es definitv .... also da laesst er sich ned ausm tritt bringen. Zumindest der vs2005 den wir momentan haben.

Mit dem vs6 dem uralt teil, gabs aber definitv probleme, ned nur mit den ueber dll grenzen, sondern auch threads und new / und delete (ja mit der MT runtime, der kannte ja mehrere, also auch eine die gar kein multithreading konnte) ....

Das ganze hat mit c++ aber eigentlich auch ned viel zu tun. Im standard (c++) wirst ned viel drueber lesen, weil threading kein thema von c++ ist.
such also besser nach malloc/free (meistens is new damit implementiert) und threads, da solltest ne menge finden.

Das ganze haengt eigentlich nich mal am Compiler, sondern nur am speichermanager selber. Den kannst ja eh ausstauschen (inlcude pfade umtauschen und ne andere runtime anbinden).

für VS gibts paar, fuers debuggen fuer ne bessere optimierung ...etc. Ob und wie die dinger mit threads zurechtkommen, iss also ggf. Problem des verwendeten Managers. Such mal nach SmartHeap und sowas, da gibts einige Dinge fuer....

Und hier hasst natuerlich recht, die meisten koennens ....

Ciao ....