Skip to content

🏗️ Einführung in Concurrency

🚀 1. Einführung in Concurrency

Concurrency (Nebenläufigkeit) bezeichnet die Fähigkeit eines Systems, mehrere Aufgaben gleichzeitig auszuführen. Moderne Betriebssysteme und Multi-Core-Prozessoren machen Concurrency zu einem wesentlichen Konzept.

Warum Concurrency?

  • Effizienz: Optimale Nutzung von CPU-Kernen.
  • Reaktionsfähigkeit: Erhöht die Interaktivität von Anwendungen.
  • Skalierbarkeit: Bessere Performance auf Multi-Core-Systemen.

🧵 2. Threads: Grundlagen & API

Was sind Threads?

Ein Thread ist eine leichtgewichtige Untereinheit eines Prozesses. Mehrere Threads eines Prozesses teilen sich denselben Speicherraum.

Threads in verschiedenen Sprachen

#include <pthread.h> 
#include <stdio.h> 
void* thread_function(void* arg) { 
    printf("Hello from thread!\\n"); return NULL; 
} 
int main() { 
    pthread_t thread; pthread_create(&thread, NULL, thread_function, NULL);
    pthread_join(thread, NULL); return 0; 
}
class MyThread extends Thread {
    public void run() {
        System.out.println("Hello from thread!");
    }
}

public class Main { 
    public static void main(String[] args) { 
        MyThread t = new MyThread(); t.start(); 
    } 
}
import threading

def thread_function():
    print("Hello from thread!")

thread = threading.Thread(target=thread_function)
thread.start()
thread.join()

⚠️ 3. Race Conditions & Synchronisation

Race Condition

Ein Race Condition tritt auf, wenn mehrere Threads gleichzeitig auf eine geteilte Ressource zugreifen und das Ergebnis von der Reihenfolge der Zugriffe abhängt.

Beispiel einer Race Condition in C

```c
#include <pthread.h>
#include <stdio.h>

int counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        counter++;  // 🔴 Race Condition! !!FIX MIT LOCKS!!
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("Counter: %d\\n", counter);  // ❌ Inkonsistentes Ergebnis!
    return 0;
```

Lösung: Synchronisation mit Locks

void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&lock);
        counter++;
        pthread_mutex_unlock(&lock);
    }
}
java class Counter { 
    private int count = 0;

    public synchronized void increment() { 
        count++; 
    } 
}
counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:
        counter += 1

🔐 4. Locks, Semaphoren & Deadlocks

🔒 Mutex (Mutual Exclusion)

Ein Mutex erlaubt nur einem Thread den Zugriff auf eine kritische Sektion.

🚦 Semaphore

Ein Semaphore begrenzt die Anzahl der Threads, die gleichzeitig auf eine Ressource zugreifen können.

Beispiel eines Semaphores in C

    #include <semaphore.h>
    sem_t semaphore;

    void* worker(void* arg) {
        sem_wait(&semaphore);  // Eintritt in kritische Sektion
        printf("Thread in kritischer Sektion\\n");
        sem_post(&semaphore);  // Verlassen der kritischen Sektion
    }

🐞 Deadlocks & Avoidance

Ein Deadlock tritt auf, wenn zwei Threads auf Ressourcen warten, die der jeweils andere blockiert.

Deadlock-Szenario

  1. Thread A hält Lock 1, wartet auf Lock 2.
  2. Thread B hält Lock 2, wartet auf Lock 1.

Lösung:

  • Locking-Order: Immer in der gleichen Reihenfolge sperren.
  • Deadlock-Erkennung: OS kann zirkuläre Wartezustände auflösen.

5. Thread-Kommunikation & Scheduling

Thread-Scheduling

  • Preemptive Scheduling: OS kann Threads unterbrechen.
  • Cooperative Scheduling: Threads geben die Kontrolle explizit zurück.

Bedingungsvariablen (Condition Variables)

Erlauben es Threads, zu warten, bis eine Bedingung erfüllt ist.

Beispiel in C mit pthread_cond_t

pthread_cond_t cond;
pthread_mutex_t lock;

void* producer(void* arg) {
    pthread_mutex_lock(&lock);
    pthread_cond_signal(&cond);  // Weckt einen wartenden Thread auf
    pthread_mutex_unlock(&lock);
}

🛠️ 6. Debugging von Concurrency-Fehlern

Häufige Concurrency-Fehler

  • Race Conditions → Verwenden von Locks/Semaphoren.
  • Deadlocks → Lock-Order festlegen.
  • Starvation → Fairness durch Prioritätsregeln sicherstellen.

Tools für Debugging

  • Valgrind (Helgrind) für C: Erkennung von Race Conditions.
  • ThreadSanitizer für C++ & Rust: Detektiert Data Races.
  • GDB für Threads: Debugging auf niedriger Ebene.

7. Best Practices für Multi-Threaded-Programmierung

🔹 Best Practices

✅ Verwende Mutexe oder Semaphoren, um kritische Abschnitte zu schützen.
✅ Nutze Thread-Pools, um Performance zu optimieren.
✅ Setze Atomic Operations ein, wenn nur einfache Werte modifiziert werden müssen.
✅ Vermeide zu viele Locks, um Deadlocks zu verhindern.
✅ Nutze Parallelisierungs-Patterns wie Map-Reduce oder Worker-Threads.


📌 Fazit

  • Concurrency ist essenziell für moderne Software.
  • Threads verbessern Performance, erfordern aber Synchronisation.
  • Race Conditions und Deadlocks sind typische Probleme.
  • Debugging-Tools helfen, Fehler frühzeitig zu erkennen.
  • Gute Planung und Best Practices sind entscheidend für stabile Programme.