Prozesssynchronisation

In diesem Beitrag geht es um die Prozesssynchronisation. Es gibt zwei Grundlegende, verschiedene Arten der Prozesssynchronisation. Es geht um die Wechselwirkung von Aktivitätsträgern (Threads/Prozesse). Zum einen gibt es die Kooperation, welche z.B. bei Video Decoder und Video Renderer der Fall ist. Die Prozesse arbeiten eng zusammen und warten aufeinander und kooperieren somit. Des weiteren gibt es die Konkurrenz. Zwei Threads kämpfen quasi um Ressourcen wie z.B. ein Gerät oder die CPU. Hier besteht Bedarf auf Synchronisation und Koordination der Aktivitätsträgern (Prozesse/Threads).

Dabei gibt es einige Nebenläufigkeitsprobleme:

  • Verklemmung (Deadlock) => Mehrere blockierte Threads warten aufeinander, sind dabei aber dauerhaft gesperrt
  • Livelock => Mehrere aktive Prozesse behindern sich derart ungeschickt gegenseitig, dass sie allsamt keinen Fortschritt mehr machen. (Man steckt quasi in einer Endlosschleife. Aktive Form des Deadlocks)
  • Verhungerung (Starvation) => Prozesse erhalten mangels Fairness nie Rechenzeit und „verhungern“

Kritische Abschnitte (Critical Sections)

Kritische Abschnitte werden benötigt, wenn bestimme Codeabschnitte nicht Nebenläufig von verschiedenen Threads ausgeführt werden dürfen, z.B. weil sie die selben Daten manipulieren.

Wenn man einen kritischen Abschnitt betritt, ist man mit Sicherheit der Einzige, der gerade an bestimmten Daten etwas ändert. Kritische Abschnitte haben einen geringen Overhead (Benötigen geringen Rechenaufwand, wenn man nicht warten muss).

Typische Operationen

  • Enter() / Lock() / Begin() => Kritischen Abschnitt betreten
  • TryEnter() / TryLock() / TryBegin() => Kritischen Abschnitt betreten falls möglich
  • Exit() / Leave() / Unlock() / End() => Kritischen Abschnitt verlassen

Beispiel für einen Kritischen Abschnitt

class SynchronizedQueue {
    private Queue MyQ = new Queue();
    public void append(valuetype value) {
        l.lock();
        Myq.append(value);
        l.unlock();        
    }
    public valutype remove() {
        l.lock();
        wert = MyQ.remove();
        l.unlock();
        return wert;
    }
}

Aktives Warten (Busy Waiting/Spin Lock)

Beim Aktiven warten, wird fortwährend probiert einen kritischen Abschnitt zu betreten. Der Nachteil davon ist, dass man Rechenzeit verbraucht, ohne einen wirklichen Nutzen davon zu bekommen. Der Einsatz erfolgt oft in Multiprozessorsystemen und bei Systemen, die eine hohe Erfolgswarscheinlichkeit aufbringen.
Wird Aktives Warten in einem System mit Zeitscheiben ausgeführt, wird beim Aktiven Warten immer eine ganze Zeitscheibe verbraucht. Beim Aktiven Warten blockiert man Prozesse nicht, sondern lässt sie einfach laufen.

Bei Kritischen Abschnitten geht es insgesamt darum, dass Ressourcen und Daten gesperrt werden können und Prozesse somit alleinig diese Ressourcen nutzen können.

Beispiel für Aktives Warten

lock eingang; 
lock ausgang;
richtung = leer;
aufBrucke = 0;

hin() {
    eingang.lock();
    while(richtung == her) {
        richtung = hin;
        aufBrucke++;

        uberqueren();

        ausgang.lock();
        aufBrucke--;
        if (aufBrucke == 0) {
            richtung = leer;
        }
        ausgang.unlock();
    }
}
her() {
    eingang.lock();
    while(richtung == hin) {
        richtung = her;
        aufBrucke++;
        eingang.unlock();

        uberqueren();

        ausgang.lock();
        aufBrucke--;
        if (aufBrucke == 0) {
            richtung = leer;
        }
        ausgang.unlock();
    }
}

Semaphore

Eine Semaphore lässt abgezählte Aktivitätsträger gleichzeitig Zugriff auf die Ressource und ist somit den Kritischen Abschnitten relativ Ähnlich. Also können z.B. mit einer Semaphore 5 Leute gleichzeitig den Kritischen Abschnitt betreten, ein weiterer kann nur eintreten, wenn vorher einer wieder raus geht. Es haben also immer maximal 5 (Beliebige Zahl) Zugriff auf die Ressourcen.

Typische Operationen

  • int(z) => Mit dem Zählwert für den Startwert ab dem gezählt wird, Optional ein Maximum, wie viele maximal reinkommen.
  • P() / Proberen() / Reservieren() / Wait() / Down() => Recource belegen
  • V() / Verhogen() / Freigeben() / Signal() / Post() / Up() => Ressouce freigeben

Beispiel für eine Semaphore

stuhl(0)
tisch(0)
plaetze(12)
rampe(1)

Proccess_T {
while (TRUE) {
    down(tisch)
    down(rampe)
    <zur Rampe fahren>
    <1 Tisch aufladen>
    up(plaetze)
    <Rampe verlassen>
    up(rampe)
}
}

Proccess_S {
while (TRUE) {
    down(stuhl)
    down(rampe)
    <Zur Rampe fahren>
    <1 Stuhl aufladen>
    up(plaetze)
    <Rampe verlassen>
    up(rampe)
}
}

Proccess_F {
while (TRUE) {
    down(plaetze)
    down(plaetze)
    down(plaetze)
    down(rampe)
    <Zur Rampe fahren>
    <1 Tisch abladen>
    up(tisch)
    <2 Stühle aufladen>
    up(stuhl)
    up(stuhl)
    <Rampe verlassen>
    up(rampe)
}
}

Ereignisobjekte (Events/Condition Variables)

Zu guter letzt gibt es noch die Ereignisobjekte. Es wird explizit ein Ereignis an wartende Prozesse/Threads signalisiert. Das genaue Verhalten ist unterschiedlich je nach Implementierung.

  • Wait() => Warten auf das Ereignis
  • Set() / Signal() / Pulse() => Ereignis signalisieren
  • Clear() / Reset() => Gespeichertes Ereignis löschen

 

Marvin Sengera

Marvin Sengera

Hey! Ich bin Marvin Sengera, Inhaber der Internetagentur "Binärfabrik" aus Paderborn. Ich habe mein Bachelorstudium Informatik mit Schwerpunkt Industriespionage an der Hochschule Hamm Lippstadt abgeschlossen und absolviere derzeit meinen Master in Fachrichtung "Technical Entrepreneurship and Innovation". Ich beschäftige mich rund um die Themen Informatik, Innovation & Unternehmensgründung.

Das könnte Dich auch interessieren …

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.