Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

Informatik 4 (StuPO90)/Semaphoren

< Informatik 4 (StuPO90)
Version vom 11. Mai 2006, 14:13 Uhr von Martin Häcker (Diskussion) (A Pattern Language for the usage of Semaphores: fast alle todo's bearbeitet)

TODOs

  • klarer machen wie man die patterns auseinander entwickelt
    • ich hab, dann will ich, daher mache ich...

A Pattern Language for the usage of Semaphores

(Nach http://c2.com/cgi/wiki?PatternLanguage)

Dafür zu sorgen, dass mehrere nebenläufige Prozesse korrekt miteinander arbeiten, ist erstaunlich schwierig. Speziell Mehrseitige Synchronisation bereitete mir beim Verstehen und Erklären besondere Probleme. Um die dabei auftretenden Probleme übersichtlicher zu erklären habe ich diese "Pattern Language" formuliert.

Ich würde mich sehr über Feedback (mhaecker@cs.tu-berlin.de) zu den Beispielen und Formulierungen freuen.

Folgende Probleme werden behandelt:

  • Locking für kritische Sektionen
  • Locking für beschränkten Zugriff (n clients)
  • Locking zum gegenseitigen Ausschluss von zwei Gruppen
  • Locking für Priorisierung einer von mehreren Gruppen

Zu diesem Zweck werden hier in der Vorlesung Semaphoren eingesetzt, die eine P() (Passieren) und eine V() (Vreigeben) Operation haben. Ausserdem können Semaphoren mit einem Wert initialisiert werden der Vorgibt wie oft P() aufgerufen werden kann, bevor der Aufrufer Blockiert wird. Für genauere Erklärungen, siehe das Informatik 4 Skript.

1. "Critical Section"

Eine kritische Sektion ist ein Stück Code, in dem nur ein Prozess gleichzeitig sein darf. Eine Möglichkeit das durchzusetzen, ist diese Bereiche mit Semaphoren zu schützen.

Beispiel:

criticalSection = Semaphore(1)

criticalSection.P()
	.. etwas in der kritischen Sektion tun ..
criticalSection.V()

2. "Restricted Section"

Will man einer bestimmten Anzahl von Prozessen den Aufenthalt in einem Bereich gestatten, nennt man das eine beschränkte Sektion. Dies setzt man um, indem man diese Sektion in die P()- und V()-Operationen einer Semaphore fasst, die mit dem gewünschten Wert initialisiert ist. Streng genommen ist dies also eine Erweiterung der "Critical Section".

Beispiel:

restrictedSection = Semaphore(n)

restrictedSection.P()
	... etwas tun in der kritischen Sektion
restrictedSection.V()

Jetzt können maximal n Prozesse den kritischen Abschnitt gleichzeitig betreten.

3. "Mutual Exclusion"

Wenn ein Stück Code nur von jeweils einer Art von Prozessen betreten werden darf, dann verwendet man eine Semaphore um gegenseitigen Ausschluss zu realisieren.

Beispiel:

wenn ich der erste Prozess meiner Art bin der den kritischen Abschnitt betreten will:
	hole ich mir die Semaphore

.. etwas im kritischen Abschnitt tun ..

wenn ich der letzte Prozess meiner Art bin der den kritischen Abschnitt verlässt:
	gebe ich die semaphore frei

Der Test ob man der erste, bzw. letzte seiner Art ist, macht man üblicherweise über einen Zähler, der nach dem Inkrementieren, bzw. Dekrementieren abgefragt wird. Damit wird sowohl der Eingangs-, als auch der Ausgangsbereich selbst eine Critical Section und muss entsprechend behandelt werden.

criticalSection = Semaphore(1), mutualExclusion = Semaphore(1)

criticalSection.P()
	howManyInCriticalSection++
	if 1 == howManyInCriticalSection:
		mutualExclusion.P()
criticalSection.V()

.. etwas in dem kritischen Abschnitt erledigen,
	 in dem jetzt diese Prozesse unter sich sind ...

criticalSection.P()
	howManyInCriticalSection--
	if 0 == howManyInCriticalSection:
		mutualExclusion.V()
criticalSection.V()

Hier wird das Muster "Critical Section" zusätzlich gebraucht, damit der Zähler zwischen seiner Veränderung und dem Test welchen Wert er hat nicht von einem anderen Prozess verändert werden kann.

Kombiniert man "Critical Section" mit "Mutual Exclusion" dann kann man entweder die "Critical Section" direkt um den kritischen Abschnitt legen, oder sie um das gesamte außen herum "wickeln".

Alle weiteren Prozessgruppen haben die selbe mutualExclusion Semaphore, aber eigene criticalSection Semaphoren und eigene Zähler.

4. "Priority"

Zwei ineinander verschachtelte Semaphoren können genutzt werden, um eine Art von Prozessen gegen andere zu priorisieren.

Beispiel:

äusserer kritischer Abschnitt <- An dieser Semaphore bleiben alle Prozesse "hängen" bis auf einen
	innerer kritischer Abschnitt <- an dieser Semaphore ist also immer nur maximal ein Prozess gleichzeitig

		.. kritischer Bereich ..

	innerer Kritischer Abschnitt
äusserer kritischer Abschnitt

Die Semaphore, die den inneren kritischen Abschnitt schützt, kann jetzt von einem anderen Prozess aus einer anderen Prozessgruppe gesperrt werden. Wenn das passiert, dann muss er maximal so lange warten, bis der Prozess, der sie gerade besitzt wieder freigibt. Da nur einer aus dieser Gruppe daran "hängen" kann, werden alle Prozesse überholt, die an der äusseren Semaphore warten.

Etwas Formaler:

outerCriticalSection = Semaphore(1), innerCriticalSection = Semaphore(1)

outerCriticalSection.P()
	innerCriticalSection.P()
		
		.. kritischer Bereich ..
	
	innerCriticalSection.V()
outerCriticalSection.V()

Will man "Priority" mit "Mutual Exclusion" kombinieren, dann wird der erste Block der "Mutual Exclusion" in den kritischen Bereich der "Priority" verlegt. Die priorisierten Prozesse, sperren dann zusätzlich zu der Ausschluss-Semaphore dann noch die Semaphore des inneren kritischen Abschnitts.

Die Niederprioren Prozesse:

outerCriticalSection = Semaphore(1), innerCriticalSection = Semaphore(1), lowPriorityCounterProtection = Semaphore(1), mutex = Semaphore(1)

outerCriticalSection.P()
	innerCriticalSection.P()
		lowPriorityCounterProtection.P()
			lowPriorityCounter++
			if 1 == lowPriorityCounter
				mutex.P()
		lowPriorityCounterProtection.V()
	innerCriticalSection.V()
outerCriticalSection.V()

.. critical section ..

lowPriorityCounterProtection.P()
	lowPriorityCounter--
	if 0 == lowPriorityCounter
		mutex.V()
lowPriorityCounterProtection.V()

Die hochprioren Prozesse:

highPriorityCounterProtection = Semaphore(1)
// "mutex" und "innerCriticalSection" sind global und werden hier mitbenutzt

highPriorityCounterProtection.P()
	highPriorityCounter++
	if 1 == highPriorityCounter
		innerCriticalSection.P()
		mutex.P()
highPriorityCounterProtection.V()

.. critical section ..

highPriorityCounterProtection.P()
	highPriorityCounter--
	if 0 == highPriorityCounter
		mutex.V()
		innerCriticalSection.V()
highPriorityCounterProtection.V()

Will man "Priority" mit "Restricted Section" kombinieren, ist zu beachten, das die begrenzte kritische Sektion der Gruppe die überholt werden soll natürlich den gesamten Code mit einschließt, der die Priorität sichert. Tut man das nicht, kann das Überholen nicht garantiert werden. Auf der anderen Seite, bei den Prozessen, die überholen sollen, muss die begrenzte kritische Sektion innerhalb des Codes liegen, der die Priorität garantiert, da andernfalls in dem Spezialfall, in dem die Restriction auf eins liegt, das Überholen nicht funktioniert. (Vorsicht: Natürlich muss bei "Restricted Section" jede Prozessgruppe eine eigene Semaphore für die Beschränkung haben.)

/*TODO: hier einen Punkt einfügen, der einseitige Synchronisationabdeckt*/

5. "Reduction"

Die Lösungen, die man aus der Kombination dieser Muster erhält, verwenden in Spezialfällen mehr Semaphoren als unbedingt nötig. Man kann daher manchmal einige der Semaphoren weglassen, da ihre Funktion schon von einer der anderen Semaphoren erfüllt werden.