diff --git a/BS_Praktikum4.cpp b/BS_Praktikum4.cpp deleted file mode 100644 index 0af24eb..0000000 --- a/BS_Praktikum4.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @file BS_Praktikum4.cpp - * @brief Implementierung der Funktionen für das BS_Praktikum4 Projekt - */ - -#include "BS_Praktikum4.h" -#include - -void sayHello() { - std::cout << "Hallo aus der BS_Praktikum4 Implementierung!" << std::endl; -} diff --git a/BS_Praktikum4.h b/BS_Praktikum4.h deleted file mode 100644 index 5fdb736..0000000 --- a/BS_Praktikum4.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file BS_Praktikum4.h - * @brief Hauptheader für das BS_Praktikum4 Projekt - */ - -#ifndef BS_Praktikum4_H -#define BS_Praktikum4_H - -/** - * @brief Gibt eine Begrüßungsnachricht aus - */ -void sayHello(); - -#endif // BS_Praktikum4_H diff --git a/CMakeLists.txt b/CMakeLists.txt index c825721..ae9611c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,11 @@ file(GLOB SOURCES *.cpp) file(GLOB HEADERS *.h *.hpp) # Ausführbare Datei erstellen -add_executable(${PROJECT_NAME} ${SOURCES}) +add_executable(${PROJECT_NAME} ${SOURCES} + ring_buffer.h + analysis_model.h + sensor_network.cpp + sensor_network.h) # Installation konfigurieren install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/README.md b/README.md index e69de29..a5689b8 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,155 @@ +/* ### Erklärung der Praktikumsaufgabe (Synchronisation) + +#### **Kernziel der Aufgabe** +Sie sollen ein **multithreaded Sensornetzwerk** simulieren, das drei Komponenten umfasst: +1. **Sensoren (Producer)** + - Erzeugen regelmäßig Messdaten (z.B. Temperatur) als Zufallszahlen. + - Speichern Daten in einem **gemeinsamen Ringpuffer** (Thread-sicher synchronisiert). +2. **Analyse-Module (Consumer & Reader)** + - Entnehmen Daten aus dem Ringpuffer. + - Lesen **gleichzeitig** ein zentrales Analysemodell (z.B. Kalibrierungsdaten). +3. **System-Controller (Writer)** + - Aktualisiert das Analysemodell in zufälligen Abständen. + +#### **Technische Anforderungen** +1. **Synchronisationsmechanismen** + - Nutzen Sie **nur Semaphore und Mutex** (wie in der Vorlesung behandelt). + - Vermeiden Sie **Race Conditions** und **Deadlocks**. +2. **Ringpuffer** + - Vorgegebene Implementierung (nicht threadsicher!) → Sie müssen **Zugriff synchronisieren**. + - Eigenschaften: + - Größe `BUFFER_SIZE` (z.B. 8 Elemente). + - Überschreibt älteste Daten bei Vollschreiben (`ring_push`). + - Blockiert beim Lesen, wenn leer (`ring_pop`). +3. **Analysemodell** + - Einfache Implementierung als **globaler Integer** (keine komplexe Analyse). + - **Reader-Writer-Problem**: + - Mehrere Leser (Analyse-Module) dürfen **gleichzeitig** lesen. + - Writer (Controller) benötigt **exklusiven Zugriff** (kein Leser/Writer aktiv). + - **Starvation des Writers verhindern** (Controller darf nicht endlos warten). +4. **Simulation** + - Sensoren/Controller: `sleep()` + `rand()` für Intervalle. + - Ausgaben: Nutzen Sie `printf()` zur Beobachtung des Systems. + +--- + +### **Zu implementierende Synchronisation** +#### 1. **Ringpuffer (Producer-Consumer Pattern)** +- **Problem**: Paralleler Zugriff von Sensoren (Produzenten) und Analyse-Modulen (Konsumenten). +- **Lösung**: + - **Mutex**: Schützt den Puffer bei `push`/`pop` (strukturelle Integrität). + - **Semaphore `items`**: Zählt belegte Pufferplätze. + - Initialwert: `0` (leer). + - **Producer** (Sensor): Erhöht `items` **nur wenn Puffer vorher nicht voll war** (sonst Überschreiben ohne Signal). + - **Consumer** (Analyse-Modul): Wartet auf `items > 0` vor dem Lesen. + + ```c + // Pseudocode: Sensor (Producer) + pthread_mutex_lock(&buffer_mutex); + bool was_full = ring_is_full(&rb); + ring_push(&rb, new_data); + if (!was_full) sem_post(&items); // Signal an Consumer + pthread_mutex_unlock(&buffer_mutex); + + // Pseudocode: Analyse-Modul (Consumer) + sem_wait(&items); // Wartet auf Daten + pthread_mutex_lock(&buffer_mutex); + ring_pop(&rb, &data); + pthread_mutex_unlock(&buffer_mutex); + ``` + +#### 2. **Analysemodell (Reader-Writer Problem mit Fairness)** +- **Problem**: Paralleles Lesen vs. exklusives Schreiben + Verhinderung von Writer-Starvation. +- **Lösung** (Fairness mit "Turnstile"-Semaphor): + - **Semaphore `turn`**: Garantiert FIFO-Reihenfolge (Fairness). + - **Semaphore `rw_mutex`**: Schützt exklusive Schreibzugriffe. + - **Mutex `read_mutex`**: Schützt Leser-Zähler (`read_count`). + - **Integer `read_count`**: Zählt aktive Leser. + + ```c + // Pseudocode: Analyse-Modul (Reader) + sem_wait(&turn); // Stelle dich in Warteschlange + sem_post(&turn); // Weiterleitung (sofort) + pthread_mutex_lock(&read_mutex); + read_count++; + if (read_count == 1) sem_wait(&rw_mutex); // Erster Leser sperrt Writer + pthread_mutex_unlock(&read_mutex); + + // LESEZUGRIFF auf shared_model + + pthread_mutex_lock(&read_mutex); + read_count--; + if (read_count == 0) sem_post(&rw_mutex); // Letzter Leser erlaubt Writer + pthread_mutex_unlock(&read_mutex); + + // Pseudocode: Controller (Writer) + sem_wait(&turn); // Warte auf deine Reihenfolge + sem_wait(&rw_mutex); // Fordere exklusiven Zugriff an + sem_post(&turn); // Freigabe für Nächsten in Warteschlange + + // SCHREIBZUGRIFF auf shared_model + + sem_post(&rw_mutex); // Freigabe + ``` + +--- + +### **Test und Reflexion** +Starten Sie das System mit verschiedenen Parametern und beobachten Sie: +1. **Durchsatzrate** + - Wie viele Daten werden pro Zeiteinheit verarbeitet? + - Variieren Sie: Puffergröße, Anzahl der Sensoren/Analyse-Module. +2. **Starvation/Deadlocks** + - Tritt Starvation auf (z.B. Controller wird blockiert)? + - Verursachen bestimmte Konfigurationen Deadlocks? +3. **Lastszenarien** + - **Viele Sensoren**: Läuft der Puffer über? (Datenverlust durch Überschreiben) + - **Viele Leser**: Wird der Controller blockiert? Wie wirkt sich Fairness aus? +4. **Systemunterbrechungen** + - Simulieren Sie OTA-Updates: Controller führt `sleep()` während Schreibzugriff aus → Blockiert Leser? +5. **Optimale Parameter** + - Finden Sie Konfigurationen, bei denen das System besonders effizient ist. + +--- + +### **Umsetzungshinweise** +- **Globale Variablen**: + ```c + RingBuffer rb; // Ringpuffer + int shared_model = 0; // Analysemodell (Integer) + sem_t items, rw_mutex, turn; // Semaphore + pthread_mutex_t buffer_mutex, read_mutex; // Mutexe + int read_count = 0; // Zähler aktiver Leser + ``` +- **Thread-Erstellung**: + ```c + // Beispiel: Starte 2 Sensoren, 3 Analyse-Module, 1 Controller + pthread_t sensor_threads[2], analyser_threads[3], controller_thread; + for (int i = 0; i < 2; i++) pthread_create(&sensor_threads[i], NULL, sensor_func, NULL); + for (int i = 0; i < 3; i++) pthread_create(&analyser_threads[i], NULL, analyser_func, NULL); + pthread_create(&controller_thread, NULL, controller_func, NULL); + ``` + +- **Wichtig**: Initialisieren Sie alle Synchronisationsmittel vor Thread-Start! + ```c + sem_init(&items, 0, 0); // Initialwert 0 + sem_init(&rw_mutex, 0, 1); // Initialwert 1 (frei) + sem_init(&turn, 0, 1); // Initialwert 1 (FIFO-Reihenfolge) + pthread_mutex_init(&buffer_mutex, NULL); + pthread_mutex_init(&read_mutex, NULL); + ``` + +--- + +### **Warum diese Lösung?** +- **Ringpuffer**: Produzenten überschreiben alte Daten, Konsumenten warten bei Leerpuffer → Effizient & verlusttolerant. +- **Analysemodell**: + - `turn`-Semaphor verhindert Writer-Starvation (Fairness). + - Leser können parallel arbeiten, solange kein Writer aktiv ist. +- **Synchronisationsmittel**: Beschränkt auf Semaphore/Mutex (vorgabekonform). + +Mit dieser Struktur erfüllen Sie alle Lernziele: +✅ Reader-Writer-Problem +✅ Producer-Consumer-Pattern +✅ Vermeidung von Race Conditions & Deadlocks! +*/ \ No newline at end of file diff --git a/analysis_model.h b/analysis_model.h new file mode 100644 index 0000000..4b4be10 --- /dev/null +++ b/analysis_model.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +/** + * Implementiert das Reader-Writer Problem mit: + * - Mehrere gleichzeitige Leser + * - Exklusiver Zugriff für Schreiber + * - Verhindert Writer-Starvation + */ +class AnalysisModel { + int value = 0; // Das geteilte Analysemodell (vereinfacht) + int reader_count = 0; // Zählt aktive Leser + + // Synchronisationsprimitive + std::mutex model_mutex; // Schützt Schreibzugriffe (exklusiv) + std::mutex count_mutex; // Schützt Leserzähler + std::condition_variable no_writer; // Garantiert Fairness + +public: + /** + * Lesender Zugriff + * @return Aktueller Wert des Modells + * + * Funktionsweise: + * 1. Sperrt count_mutex und inkrementiert reader_count + * 2. Erster Leser sperrt model_mutex (blockiert Writer) + * 3. Entsperrt count_mutex während des Lesens + * 4. Liest Wert + * 5. Sperrt count_mutex zum Dekrementieren + * 6. Letzter Leser entsperrt model_mutex und benachrichtigt Writer + */ + int read() { + std::unique_lock count_lock(count_mutex); + reader_count++; + + // Erster Leser sperrt für Writer + if(reader_count == 1) { + model_mutex.lock(); + } + count_lock.unlock(); + + // Kritischer Abschnitt (Lesen, kann parallel erfolgen) + int result = value; + + count_lock.lock(); + reader_count--; + // Letzter Leser gibt für Writer frei + if(reader_count == 0) { + model_mutex.unlock(); + no_writer.notify_one(); + } + + return result; + } + + /** + * Schreibender Zugriff + * @param new_value Neuer Wert für das Modell + * + * Funktionsweise: + * 1. Sperrt model_mutex (exklusiver Zugriff) + * 2. Schreibt neuen Wert + * 3. Wartet bis alle Leser fertig sind (Starvation Prevention) + */ + void write(int new_value) { + std::unique_lock lock(model_mutex); + value = new_value; + + // Verhindert Writer-Starvation + no_writer.wait(lock, [this]() { + return reader_count == 0; + }); + } +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp index 6b4677f..b608ad5 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,90 @@ -/** - * @file main.cpp - * @brief Hauptdatei für das BS_Praktikum4 Projekt - */ - +#include "sensor_network.h" #include -#include "BS_Praktikum4.h" +#include +#include +#include + +// Standardkonfiguration +constexpr size_t DEFAULT_NUM_SENSORS = 3; +constexpr size_t DEFAULT_NUM_ANALYSERS = 2; +constexpr int DEFAULT_RUN_TIME = 30; // Sekunden +constexpr size_t DEFAULT_BUFFER_SIZE = 8; + +/** + * Führt die Simulation mit gegebenen Parametern aus + * @tparam N Puffergröße + */ +template +void run_simulation(size_t num_sensors, size_t num_analysers, int run_time) { + SensorNetwork network; + std::cout << "\n=== Simulation gestartet ===\n" + << "Sensoren: " << num_sensors << "\n" + << "Analysemodule: " << num_analysers << "\n" + << "Puffergröße: " << N << "\n" + << "Laufzeit: " << run_time << "s\n\n"; + + network.start(num_sensors, num_analysers); + std::this_thread::sleep_for(std::chrono::seconds(run_time)); + network.stop(); + + std::cout << "\n=== Simulation beendet ===\n"; +} + +/** + * Liest Benutzereingabe mit Standardwert + * @param prompt Eingabeaufforderung + * @param default_value Standardwert bei leerer Eingabe + * @return Eingegebener oder Standardwert + */ +size_t get_input(const std::string& prompt, size_t default_value) { + std::cout << prompt << " [" << default_value << "]: "; + std::string input; + std::getline(std::cin, input); + + // Verwende Standardwert bei leerer Eingabe + if(input.empty()) return default_value; + + // Konvertiere Eingabe + try { + return std::stoul(input); + } catch(...) { + std::cout << "Ungültige Eingabe. Verwende Standardwert: " + << default_value << "\n"; + return default_value; + } +} int main() { - std::cout << "Willkommen zum BS_Praktikum4 Projekt!" << std::endl; - - // Beispielaufruf einer Funktion aus der Header-Datei - sayHello(); - + std::cout << "=== Sensornetzwerk-Simulation ===\n" + << "(Leere Eingabe verwendet Standardwerte)\n"; + + // Interaktive Konfiguration + size_t num_sensors = get_input("Anzahl Sensoren", DEFAULT_NUM_SENSORS); + size_t num_analysers = get_input("Anzahl Analysemodule", DEFAULT_NUM_ANALYSERS); + int run_time = static_cast( + get_input("Laufzeit (Sekunden)", DEFAULT_RUN_TIME) + ); + size_t buffer_size = get_input("Puffergröße", DEFAULT_BUFFER_SIZE); + + // Starte Simulation basierend auf Puffergröße + switch(buffer_size) { + case 8: + run_simulation<8>(num_sensors, num_analysers, run_time); + break; + case 16: + run_simulation<16>(num_sensors, num_analysers, run_time); + break; + case 32: + run_simulation<32>(num_sensors, num_analysers, run_time); + break; + default: + std::cout << "Nicht unterstützte Puffergröße. Verwende Standard (" + << DEFAULT_BUFFER_SIZE << ")\n"; + run_simulation( + num_sensors, num_analysers, run_time + ); + } + + std::cout << "Simulation erfolgreich abgeschlossen.\n"; return 0; -} +} \ No newline at end of file diff --git a/ring_buffer.h b/ring_buffer.h new file mode 100644 index 0000000..ee992c8 --- /dev/null +++ b/ring_buffer.h @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include +#include + +/** + * Thread-sicherer Ringpuffer mit fester Größe + * @tparam N Größe des Puffers (muss > 1 sein) + * + * Implementiert das Producer-Consumer Pattern mit: + * - Mutex für exklusiven Zugriff + * - Condition Variable für blockierendes Pop + * - Überschreibt älteste Daten bei vollem Puffer + */ +template +class RingBuffer { + static_assert(N > 1, "Buffer size must be greater than 1"); + +private: + std::vector data; // Speicher für Elemente + size_t read_ptr = 0; // Lesezeiger (nächstes zu lesendes Element) + size_t write_ptr = 0; // Schreibzeiger (nächstes freie Position) + bool full = false; // Flag, ob Puffer voll ist + + // Synchronisationsprimitive + std::mutex mtx; // Schützt alle internen Zustände + std::condition_variable not_empty; // Signalisiert, dass Daten verfügbar sind + + // Hilfsfunktion: Zeiger mit Ringverhalten bewegen + size_t advance(size_t ptr) const { + return (ptr + 1) % N; + } + +public: + RingBuffer() : data(N, 0) {} + + /** + * Schreibt Wert in den Puffer + * @param value Der zu schreibende Wert + * + * Funktionsweise: + * 1. Sperrt Mutex für exklusiven Zugriff + * 2. Schreibt Wert an aktueller write_ptr + * 3. Bei vollem Puffer: Bewegt read_ptr (überschreibt ältesten Wert) + * 4. Aktualisiert write_ptr und full-Flag + * 5. Benachrichtigt einen wartenden Consumer + */ + void push(int value) { + std::unique_lock lock(mtx); + + // Schreibe Wert + data[write_ptr] = value; + + // Überschreibe ältesten Wert bei vollem Puffer + if(full) { + read_ptr = advance(read_ptr); + } + + // Zeiger aktualisieren + write_ptr = advance(write_ptr); + full = (write_ptr == read_ptr); + + // Benachrichtige einen wartenden Consumer + not_empty.notify_one(); + } + + /** + * Liest Wert aus dem Puffer (blockierend) + * @return Der gelesene Wert + * + * Funktionsweise: + * 1. Sperrt Mutex + * 2. Wartet mit Condition Variable bis Daten verfügbar + * 3. Liest Wert an read_ptr + * 4. Aktualisiert read_ptr und full-Flag + * 5. Gibt Wert zurück + */ + int pop() { + std::unique_lock lock(mtx); + + // Warte bis Daten verfügbar (verhindert Busy Waiting) + not_empty.wait(lock, [this]() { + return !is_empty(); + }); + + // Lese und aktualisiere Zustand + int value = data[read_ptr]; + read_ptr = advance(read_ptr); + full = false; + + return value; + } + + // Prüft ob Puffer leer ist + bool is_empty() const { + return !full && (read_ptr == write_ptr); + } + + // Prüft ob Puffer voll ist + bool is_full() const { + return full; + } +}; \ No newline at end of file diff --git a/sensor_network b/sensor_network new file mode 100755 index 0000000..a918fbc Binary files /dev/null and b/sensor_network differ diff --git a/sensor_network.cpp b/sensor_network.cpp new file mode 100644 index 0000000..038a7ca --- /dev/null +++ b/sensor_network.cpp @@ -0,0 +1,138 @@ +#include "sensor_network.h" +#include +#include +#include + +/** + * Startet alle Threads des Netzwerks + * @param num_sensors Anzahl der Sensor-Threads + * @param num_analysers Anzahl der Analyse-Threads + */ +template +void SensorNetwork::start(size_t num_sensors, size_t num_analysers) { + running = true; + + // Starte Sensor-Threads + for(size_t i = 0; i < num_sensors; ++i) { + sensors.emplace_back([this, i] { + sensor_thread(i); + }); + } + + // Starte Analyse-Threads + for(size_t i = 0; i < num_analysers; ++i) { + analysers.emplace_back([this, i] { + analyser_thread(i); + }); + } + + // Starte Controller-Thread + controller = std::thread([this] { + controller_thread(); + }); +} + +/** + * Stoppt alle Threads und wartet auf Beendigung + */ +template +void SensorNetwork::stop() { + running = false; + + // Warte auf Thread-Ende + for(auto& t : sensors) { + if (t.joinable()) t.join(); + } + for(auto& t : analysers) { + if (t.joinable()) t.join(); + } + if (controller.joinable()) { + controller.join(); + } +} + +/** + * Thread-Funktion für Sensoren (Producer) + * @param id Eindeutige ID des Sensors + * + * Funktionsweise: + * 1. Generiert zufällige Messwerte + * 2. Wartet zufällige Zeit (Messintervall) + * 3. Schreibt Daten in Ringpuffer + */ +template +void SensorNetwork::sensor_thread(int id) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> data_gen(0, 100); // Messwerte 0-100 + std::uniform_int_distribution<> sleep_gen(100, 500); // Intervall 100-500ms + + while(running) { + // Simuliere Messintervall + std::this_thread::sleep_for( + std::chrono::milliseconds(sleep_gen(gen)) + ); + + // Generiere und schreibe Messwert + int value = data_gen(gen); + buffer.push(value); + + std::cout << "Sensor " << id << " produced: " << value << "\n"; + } +} + +/** + * Thread-Funktion für Analyse-Module (Consumer) + * @param id Eindeutige ID des Moduls + * + * Funktionsweise: + * 1. Liest Daten aus Ringpuffer (blockierend) + * 2. Liest aktuelles Analysemodell + * 3. Verarbeitet Daten (hier nur Ausgabe) + */ +template +void SensorNetwork::analyser_thread(int id) { + while(running) { + // Blockierendes Lesen aus Puffer + int data = buffer.pop(); + + // Lesender Zugriff auf Analysemodell + int model_value = model.read(); + + std::cout << "Analyser " << id << " processed: " << data + << " | Model: " << model_value << "\n"; + } +} + +/** + * Thread-Funktion für System-Controller (Writer) + * + * Funktionsweise: + * 1. Wartet zufällige Zeit zwischen Updates + * 2. Schreibt neuen Wert ins Analysemodell + */ +template +void SensorNetwork::controller_thread() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> update_gen(0, 100); // Modellwerte + std::uniform_int_distribution<> sleep_gen(500, 2000); // Update-Intervall + + while(running) { + // Warte bis zum nächsten Update + std::this_thread::sleep_for( + std::chrono::milliseconds(sleep_gen(gen)) + ); + + // Aktualisiere Analysemodell + int new_value = update_gen(gen); + model.write(new_value); + + std::cout << "Controller updated model to: " << new_value << "\n"; + } +} + +// Explizite Instanziierung für gängige Puffergrößen +template class SensorNetwork<8>; +template class SensorNetwork<16>; +template class SensorNetwork<32>; \ No newline at end of file diff --git a/sensor_network.h b/sensor_network.h new file mode 100644 index 0000000..1480dc3 --- /dev/null +++ b/sensor_network.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include "ring_buffer.h" +#include "analysis_model.h" + +/** + * Hauptklasse des Sensornetzwerks + * @tparam N Größe des Ringpuffers + * + * Verwaltet alle Komponenten: + * - Ringpuffer für Sensordaten + * - Analysemodell + * - Threads für Sensoren, Analyse und Controller + */ +template +class SensorNetwork { + RingBuffer buffer; // Gemeinsamer Datenpuffer + AnalysisModel model; // Geteiltes Analysemodell + std::atomic running{false}; // Steuerflag für Threads + + // Thread-Container + std::vector sensors; + std::vector analysers; + std::thread controller; + +public: + ~SensorNetwork() { + if (running) stop(); + } + + void start(size_t num_sensors, size_t num_analysers); + void stop(); + +private: + // Thread-Funktionen + void sensor_thread(int id); + void analyser_thread(int id); + void controller_thread(); +}; \ No newline at end of file