2 Stimmen

C++/Boost: Synchronisiere den Zugriff auf eine Ressource über mehrere Methodenaufrufe (Getter) hinweg

Ich bin mir nicht sicher, ob es sich um eine Frage zur Programmierungstechnik oder zum Design handelt, aber ich bin für Vorschläge offen.

Das Problem: Ich möchte eine Abstraktionsschicht zwischen Datenquellen (Sensoren) und Verbrauchern erstellen. Die Idee ist, dass die Verbraucher nur die Schnittstellen (abstrakte Basisklasse) verschiedener Sensortypen "kennen". Jeder dieser Sensortypen besteht in der Regel aus mehreren individuellen Werten, von denen jeder seine eigenen Getter-Methoden hat.

Als Beispiel verwende ich einen vereinfachten GPS-Sensor.

class IGpsSensor {

    public:
        virtual float getLongitude() = 0;
        virtual float getLatitude() = 0;
        virtual float getElevation() = 0;

        // Abweichungen
        virtual float getLongitudeDev() = 0;
        virtual float getLatitudeDev() = 0;
        virtual float getElevationDev() = 0;

        virtual int getNumOfSatellites() = 0;
};

Da Aktualisierungen des Sensors von einem anderen Thread durchgeführt werden (die Details obliegen der Implementierung der Schnittstelle), erscheint es vernünftig, Getter und auch die Aktualisierungsmethoden zu synchronisieren, um Konsistenz sicherzustellen.

Bisher so gut. In den meisten Fällen sollte diese Ebene der Synchronisierung ausreichen. Manchmal kann es jedoch notwendig sein, mehr als einen Wert zu erfassen (mit aufeinanderfolgenden getXXX()-Aufrufen) und sicherzustellen, dass keine Aktualisierung dazwischengeschoben wird. Ob dies erforderlich ist oder nicht (und welche Werte wichtig sind), liegt am Verbraucher.

Wenn wir beim Beispiel bleiben, ist es in vielen Fällen nur wichtig, Längen- und Breitengrad zu kennen (hoffentlich aber beide auf dasselbe update() bezogen). Ich gebe zu, dass dies durch Gruppierung in eine "Position" Klasse oder Struktur erreicht werden könnte. Aber ein Verbraucher könnte den Sensor auch für einen komplizierteren Algorithmus verwenden und benötigt damit auch die Abweichung.

Jetzt frage ich mich, was der richtige Weg wäre, dies zu tun.

Die Lösungen, an die ich dachte:

  • Alle möglichen Werte in eine Struktur (oder Klasse) gruppieren und eine zusätzliche (synchronisierte) Getter-Methode hinzufügen, die Kopien aller Werte auf einmal zurückgibt - scheint mir in Fällen, in denen nur 2 oder 3 von vielleicht 10 Werten benötigt werden, wie eine Menge unnötiger Overhead.

  • Eine Methode hinzufügen, die eine Referenz auf den im Datenquellen verwendeten Mutex zurückgibt, um das Sperren durch den Verbraucher zu ermöglichen - das fühlt sich nicht nach "gutem Design" an. Und da die Getter bereits synchronisiert sind, ist die Verwendung eines rekursiven Mutex obligatorisch. Ich gehe jedoch davon aus, dass es mehrere Leser, aber nur einen Schreiber gibt, und ich würde daher eher ein Shared Mutex verwenden.

Danke für Ihre Hilfe.

2voto

jterrace Punkte 60926

Wie wäre es, eine "Reader" Schnittstelle freizulegen? Um das Reader-Objekt zu erhalten, würden Sie etwas Ähnliches tun:

const IGpsSensorReader& gps_reader = gps_sensor.getReader();

Die Klasse IGpsSensorReader könnte auf geschützte Elemente der Klasse IGpsSensor zugreifen. Bei der Konstruktion würde sie das Schloss anfordern. Beim Abbau würde sie das Schloss freigeben. Ein Zugriffsmethoden könnte so etwas tun:

{ //Block, der Attribute abruft
   const IGpsSensorReader& gps_reader = gps_sensor.getReader();
   //Lese alle erforderlichen Werte von gps_reader
} //Durch das Schließen des Bereichs wird gps_reader zerstört und entsperrt

Sie könnten auch eine getWriter Methode für den Thread bereitstellen, der die Updates durchführt. Intern könnten Sie boost's shared_mutex verwenden, um den Zugriff zwischen den Lesern und den Schreibern zu vermitteln.

2voto

Dennis Zickefoose Punkte 10536

Eine Technik, die ich in einigen einfachen Projekten verwendet habe, besteht darin, nur Zugriff auf ein Proxy-Objekt bereitzustellen. Dieses Proxy-Objekt hält für die Dauer seines Lebensdauer eine Sperre und bietet die eigentliche Schnittstelle zu meinen Daten. Dieser Zugriff führt keine Synchronisierung selbst durch, da er nur über den Proxy verfügbar ist, der bereits angemessen gesperrt ist. Ich habe dies noch nie auf ein großes Projekt ausgeweitet, aber es hat sich gut für meine Zwecke bewährt.

1voto

zvrba Punkte 23708

Mögliche Lösung: leiten Sie alle Ihre Quellklassen ab von

class Transaction {
  pthread_mutex_t mtx;
  // Konstruktor/Destruktor
public:
  void beginTransaction() { pthread_mutex_lock(&mtx); } // FEHLERÜBERPRÜFUNG FEHLT
  void endTransaction() { pthread_mutex_unlock(&mtx); } // FEHLERÜBERPRÜFUNG DURCHFÜHREN
protected:
  // Hilfsmethode
  int getSingle(int *ptr)
  { int v; beginTransaction(); v=*ptr; endTransaction(); return v; }
};

Wenn Sie mehrere Werte auslesen müssen, verwenden Sie die begin/endTransaction Methoden. Um Ihre getValue Funktionen zu definieren, rufen Sie einfach getSingle mit einem Zeiger auf das entsprechende Element auf [dies ist nur eine Annehmlichkeitsmethode, damit Sie nicht in jeder getValue Funktion begin/endTransaction aufrufen müssen.].

Sie müssen einige Details ausarbeiten, denn wenn Ihre getValue Funktionen begin/endTransaction verwenden, können Sie sie nicht innerhalb einer Transaktion aufrufen. (Ein Mutex kann nur einmal gesperrt werden, es sei denn, er ist so konfiguriert, dass er rekursiv ist.)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X