Variabel vor gleichzeitigem Zugriff schützen

Alles rund um die Programmierung mit Qt
Antworten
bp
Beiträge: 44
Registriert: 21. Januar 2009 11:25

Variabel vor gleichzeitigem Zugriff schützen

Beitrag von bp »

Hallo,

ich habe das Problem, dass ich eine Variable vor gleichzeitigem Zugriff schützen muss.

Code: Alles auswählen

class fo
{
    public:
        void setImage(QImage *image);

    protected:
        void paintEvent(QPaintEvent *event);

    private:
        QImage *m_pImage;
}
Jetzt greife ich über eine Funktion auf die Funktion setImage zu. Da meine Funktion durch einen Funktionspointer an einen Thread übergeben wurde, kann es nun passieren, dass meine Funktion neuen speicher alloziiert und dann mit der Funktion setImage die Adresse übergibt.
Gleichzeitig versucht aber die paintEvent Funktion auf die Adresse zuzugreifen, um as Bild neu zu zeichnen. Dadurch das ich die Funktion update nicht steuern kann, wann sie aufgerufen wird, kann es vorkommen, dass der Speicher für das Bild bereits frei gegeben ist, bevor ich es in der paintEvent-Funktion gezeichnet habe.

Ein löschen in der paintEvent Funktion funktioniert auch nicht, da es passieren kann, dass mein Zeiger überschrieben wird, und ich somit den Speicher nicht mehr frei geben kann.

Code: Alles auswählen

class fo *f;



void update()
{
    if(image != NULL) delete image;

    image = new QImage(...);
    f->setImage(image);
    f->update();
}
Ich habe da mal was von mutex gehört, habe aber keine Ahnung wie ich die an dieser Stelle einsetzten könnte, da ich ja eine Variable an zwei stellen schützen muss.

Bin total verzweifelt und für jede Hilfe dankbar.

bp
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Erstell ein QImage (ohne Pointer) in deinem Thread und gib dieses per Signal-Slot-Verbindung weiter. Dann brauchst du kein QMutex und brauchst dir wegen irgendwelcher Pointer auch keine Gedanken machen.
bp
Beiträge: 44
Registriert: 21. Januar 2009 11:25

Beitrag von bp »

So,

muss jetyt doch noch mal mit dem Problem befassen.
Habe viel rumprobiert und gemacht und getan, doch hat elider nichts davon geholfen.

1. Mit Signal/Slots kann ich leider nicht arbeiten, da es leider nicht mein Thread ist, und ich aus nem fremden Thread keine signals senden kann.

2. Dann habe ich es mal damit versucht, das Bild häufiger zu kopieren um somit keine Pointer mehr nutzen zu müssen. Wurde etwas besser, doch führte auch nicht zum gewünschten Erfolg.

Zur Zeit benutz ich wieder die Pointer, die will auch mein Chef haben, da zu viel kopieren das System träge werden lässt.

Noch einmal wie ihc das ganze zur Zeit gelöst habe.

Code: Alles auswählen

void setImage(QImage *image)
{
    QMutexLocker locker(&m_Mutex);
    if(m_Image)
    {
        delete m_Image;
        m_Image = NULL;
    }
    m_Image = image;
    update();
}
dann ist da noch die paintEvent-Funktion

Code: Alles auswählen

void paintvent(QPaintEvent)
{
    QMutexLocker locker(&m_Mutex);
    QPainter painter(this);
    QImage image = m_Image->scale(....);
    painter.drawImage(0,0, image);
}
Das ist auch schon alles.
Da das bild aber ungefähr 60 mal pro Sekunde aktualisiert wird, habe ich tierische Probleme mit dem locken. Obwohl ich den QMutexLoxker nutze, wird während meiner paintEvent-Funktion auch die setImage-Funktion aufgerufen.
Das ganze klappt auch nicht mit dem locken, wenn ich nur mit

Code: Alles auswählen

m_Mutex.lock()
...
m_Mutex.unlock()
arbeite.

Bin echt für jeden Vorschlag dankbar, denn langsam läuft mir die Zeit davon.

bp
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Obwohl ich den QMutexLoxker nutze, wird während meiner paintEvent-Funktion auch die setImage-Funktion aufgerufen.
Da gibt's eigentlich nur eine Antwort: das kann gar nicht sein. Entweder interpretierst du das Verhalten (Crash?) falsch, oder der Code hat sonst ein Fehler. Probier mal das Ganze zu verifizieren mit folgendem Code (ich gehe davonaus, dass bei Dir noch ein Klassennnamen fehlte) :

Code: Alles auswählen

void MyClass::setImage(QImage *image)
{
  qDebug() << "setImage() von" << this;
  m_Mutex.lock();
  qDebug() << "setImage- kritischer Block Start";
  .....
  qDebug() << "setImage- kritischer Block Ende";
  m_Mutex.unlock();
}

void MyClass::paintvent(QPaintEvent) 
{
  qDebug() << "paintvent() von" << this;
  m_Mutex.lock();
  qDebug() << "paintvent- kritischer Block Start";
  .....
  qDebug() << "paintvent- kritischer Block Ende";
  m_Mutex.unlock();
}
Da waere ich nun sehr auf die Ausgabe im Falle des Crashes gespannt...

BTW: 60 Updates dürften haarig sein... denn scale() ist ganz schön aufwendig...
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Code: Alles auswählen

void setImage(QImage *image)
{
    QMutexLocker locker(&m_Mutex);
    if(m_Image)
    {
        delete m_Image;
        m_Image = NULL;
    }
    m_Image = image;
    update();
} 
Das Update da drinne, was macht das genau ?
Schiesst das auf irgend eine GUI funktion (Paint event) ? dann musst du eh entkoppeln und hasst keine andere Wahl. Denn wir wissen, GUI funktionen innerhalb der qt duerfen nur vom Main thread aus aufgerufen werden.

Deswegen machts auch keinen Sinn, im paintevent was locken zu wollen ^^ Das muss einfach probleme geben.
Mit Signal/Slots kann ich leider nicht arbeiten, da es leider nicht mein Thread ist, und ich aus nem fremden Thread keine signals senden kann.
klar , das ist doch auch deine crux, das dich der andere thread aufruft, oder ? das heisst du kannst in dem aufruf "einfach" einen threadwechsel herbeifuehren .... das kannst ueber signale und slots, aber auch ueber events oder ganz selbst machen.

um signale von dem nichtmainthread abzuschicken, brauchst du nicht mal eine eventqueue, die in dem nichtmainthread laeuft ! nur wenn signale empfangen willst, brauchst die ....

Einzig und allein der gueltigkeitsbereich von deinem pointer auf die daten wird ein problem, weil ueber threadgrenzen hinweg die signale assynchron arbeiten, du also im nichmainthred ned weisst, wann dein pointer vom mainthread abgearbeitet wird und dementsprechend wann ihn loeschen kannst und wann ned.

EIgentlich macht man sogar auch in nicht qt programmierung in solchen faellen oft expliziete kopien, einfach um sich verwaltungsoverhaed zu sparen. Die frage ist, wie gross das Image ist, um abzuschatzen was performanter ist.
Zur Zeit benutz ich wieder die Pointer, die will auch mein Chef haben, da zu viel kopieren das System träge werden lässt.
QImage ist eh nen pointer auf daten, das heisst intern ist das ding nen zeiger mit lazy copy funktionalitaet.
Auf grund der generizitaet und der threadsicherheit wird aber nen QIMage seine daten kopieren, wenn den ueber ne assynchrone Signal/Slot verbindung schickst.

Von woher bekommst du eigentlich die Daten fuer das bild ? Aus nem Stream ? ich wuerd wahrscheinlich das handling der Daten im multithreading context abhandeln, und das ganze GUI zuegs, also daten zu bild, und bild anzeigen im mainthread abhandeln, und schoen entkoppelt von dem dateneingang, so das man vielleicht sogar statt 60 auf 30 fps oder weniger runner takten kann ....

wie solarix scho sagt, das scale kann uebel aufewendig sein, warum das oefters machen als notwendig ^^

übrigens, wenn man perofrmant sein will, sollt man sich ueberlegen ob man zigmal ein new xyz aufruft, QImage in diesem Fall. unnoetiges und haeufiges new / malloc is ne riesen-perofrmancebremse.
Statt dem new QImage sollt man sich ueberlegen, nicht besser das alte QImage ned weiterzuverwenden und an dem einfach das load / loadFromData aufzurufen !

Ciao ...
bp
Beiträge: 44
Registriert: 21. Januar 2009 11:25

Beitrag von bp »

Hallo, danke für die Antworten.

Hier mal so der grobe Ablauf der Applikation die ich gerade schreibe.

Es gibt einen Thread, der die Daten vom Netzwerk so aufbereitet, dass ich daraus mein Image erstellen kann. Dieser ruft über diverse Funktions-Pointer unter anderem auch meine Funktion auf, die das bild verarbeitet.

von hier aus, wird das bild dann an das Fenster übergeben,

Code: Alles auswählen

void MyWindow::setImage(QImage *image)
{
    m_Mutex.lock();
    if(m_Image)
    {
        delete m_Image;
        m_Image = NULL;
    }
    m_Image = image;
    update(); 
m_Mutex.unlock()
}
welches es dann anschließend darstellen soll. Das zeichnen wird somit auch nur von dem Mainthread geregelt.

Code: Alles auswählen

void MyWindow::paintEvent(QPaintEvent *event)
{
    m_Mutex.lock();
    // Hier das zeichnen des Bildes.
    m_Mutex.unlock();
}
Das Funktioniert mitlerweile auch ganz gut.

Das mit der Bildwiederholrate kann ich nicht ändern, das kommt aus einer externen Library, auf die wir leider keinen Einfluss haben.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Nur so aus reiner Neugier: was hat sich zwischen
Das ganze klappt auch nicht mit dem locken
und
Das Funktioniert mitlerweile auch ganz gut.
verändert?
Das mit der Bildwiederholrate kann ich nicht ändern, das kommt aus einer externen Library, auf die wir leider keinen Einfluss haben.
Die GUI hast du in der Hand... nicht die Library. Was du also tun könntest wäre z.B. (wie von RHBaum bereits angedeutet) aufwendige Operationen (scale) in den Thread-Kontext zu verlagern (in der Methode "setImage()" ausführen).
Weiter würde ich "update()" nicht über "setImage()" aufrufen sondern über ein QTimer-Signal (z.B. mit 20Hz). Dann kann es dir egal sein, ob die Library mit 10, 50 oder 160Hz Bilder sendet, die GUI macht einfach immer 20 Updates/s.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Es gibt einen Thread, der die Daten vom Netzwerk so aufbereitet, dass ich daraus mein Image erstellen kann
das heisst du hasst nen Sammlerthread, der deine daten schoen ueber nen socket sammelt.
Irgendwo speicherst du die daten doch da in nem binaeren block oder ?
den wuerd ich locken, und ned das QImage. Das Qimage ist doch schon die erste kopie deiner daten ! Wenn du pech hasst, und den Threadwechsel mit nem QImage machst, macht das QImage intern selbst noch mal ne kopie seiner daten.
Kann ich Dir aber ned 100% sagen, da kenn ich die internas von QImage und QMimeType etc ned so genau ...

Machst du es, wie dein Chef dir es empfiehlt, kannst zwar die Kopie verhindern, machst aber nen "riesenaufwand" (Fehleranfaellig) mit den gueligkeitsbereichen deiner internen daten.

benutzt du fuer das empfangen der Daten ausm netz auch die Qt, also QtNetwork ?

Prinzipiell, brauchst du es wirklich performant ....
wuerd ich vielleicht die einkommenden daten abwechselnd in 2 puffer schreiben ... erstes bild in den 1. puffer, bei bildwechsel auf den anderen puffer wechseln.

dein GUI thread wuerd ich timergesteuert die daten abholen lassen, und einfach aus dem puffer lesen, der nicht grad von deinem anderen thread beschrieben wird. die puffer natuerlich sichern, und mit 2 unnerschiedlichen mutexen ....
So kannst das schoen entkoppeln, hasst releativ wenig blockiererei, aber auch wenig kopiererei(intern im GUI Mainthread kannst gut mit referenzen arbeiten) ... und konvertierst und bereitest die Daten nur auf, wenn es wirklich abrufst .... deine cpu wird es dir danken :-)

Aber davon abgesehen .... in welchen format liegt dein "Stream" vor ?
Bilderfolgen (videos) laesst besser von den codecs und API methoden (in diversen libs) direkt auf die windows handles schreiben.
Wenn das was standardiesiertes ist, wirst die performance einer darauf angepassten lib nie schlagen ! Was ist der Lieferant deiner Daten ?
moderne CAMs und TVkarten soweiso, und selbst fuer Bildschirmfotos/videos irgendeines PC's gibts libs, die Dir schon gleich nen standardisierten datenstrom liefern. Wenn du Einfluss auf den Lieferant des Bildes haben solltest, wuerd ich versuchen in die richtung zu gehen ...

Ciao ...
bp
Beiträge: 44
Registriert: 21. Januar 2009 11:25

Beitrag von bp »

Hallo,

@solarix: Habe das mit dem Absturz nicht richtig nachvollziehen können. Das funktioniert genauso wie du das gesagt hast. Es wird ordentlich gelockt und somit habe ich hier kein Pointer-Problem mehr.
Zur Zeit stürzt das Programm einfach irgendwann ab.
Wenn wir das versuchen zu debuggen läuft es immer schön durch. Müssen also da noch irgendwo ein zeitliches Problem haben.

@HBaum:
die Daten bekomme ich als YUV420 oder so geliefert. Die Daten erhalte ich von der externen llib, die das alles in RTP-Pakete enthält. Habe auch sonst keinen Einfluss darauf, da ich nur Funktionspointer nutzen kann. In meinen Funktionen wird dann schlißlich die Verarbeitung der Daten gemacht. Das mit zwei Bildern habe ich schon probiert, das anderer werde ich noch testen.

Erst mal danke für die Hilfe, wenn ich weitere Fragen habe, dann werde ich mich schon melden

bp
Antworten