AllInfo

So erstellen Sie ein Semaphor in Bash

Entwickeln Sie eine Multithread-Anwendung? Früher oder später werden Sie wahrscheinlich einen Semaphor verwenden müssen. In diesem Artikel erfahren Sie, was ein Semaphor ist, wie man einen in Bash erstellt/implementiert und vieles mehr.

Was ist ein Semaphor ?

Ein Semaphor ist ein Programmierkonstrukt, das in Computerprogrammen verwendet wird, die mehrere Verarbeitungsthreads verwenden (Computerverarbeitungsthreads, die jeweils Quellcode aus demselben Programm oder derselben Programmsuite ausführen) ausschließliche Nutzung einer gemeinsamen Ressource zu einem bestimmten Zeitpunkt. Einfacher gesagt, denken Sie darüber nach wie “einen nach dem anderen.”

Ein Semaphor wurde erstmals Ende der 1960er Jahre von dem verstorbenen Informatiker Edsger Dijkstra aus Rotterdam in den Niederlanden definiert. Sie haben in Ihrem Leben wahrscheinlich schon oft Semaphoren benutzt, ohne es ausdrücklich zu merken!

Eine Weile in den Niederlanden bleiben, einem Land voller kleiner Wasserstraßen und vieler beweglicher Brücken (eine sogenannte Zugbrücke in amerikanischem Englisch) kann man viele hervorragende Beispiele für einen Semaphor aus der realen Welt sehen; Betrachten Sie den Griff eines Zugbrückenantriebs: nach oben oder unten. Dieser Griff ist die Semaphor-Variable, die entweder die Wasserstraße oder die Straße vor Unfällen schützt. Somit könnten die Wasserstraße und die Straßen als andere Variablen angesehen werden, die durch das Semaphor geschützt werden.

Wenn der Griff oben und die Brücke geöffnet ist, wird dem Schiff die ausschließliche Nutzung der Wasser-/Straßenkreuzung gegeben oder Schiffe, die durch den Wasserkanal fahren. Wenn der Griff unten ist, ist die Brücke geschlossen und die Autos, die über die Brücke fahren, haben die ausschließliche Nutzung der Wasser-/Straßenkreuzung.

Werbung

Die Semaphor-Variable kann den Zugriff auf einen anderen Satz von Variablen steuern. Zum Beispiel verhindert der up-Zustand des Handles, dass die Variablen $cars_per_minute und $car_toll_booth_total_income aktualisiert werden usw.

Wir können das Beispiel noch etwas weiterführen und klarstellen, dass, wenn der Handle nach oben oder unten arbeitet, a Das passende Licht ist sowohl für Schiffskapitäne auf dem Wasser als auch für Personen, die in Autos und Lastwagen auf der Straße fahren, sichtbar: Alle Bediener können eine gemeinsame Variable für einen bestimmten Zustand lesen.

Das hier beschriebene Szenario ist zwar nicht nur ein Semaphor, sondern auch ein einfacher Mutex. Ein Mutex ist ein weiteres übliches Programmierkonstrukt, das einem Semaphor sehr ähnlich ist, mit der zusätzlichen Bedingung, dass ein Mutex nur von derselben Task oder demselben Thread entsperrt werden darf, der ihn gesperrt hat. Mutex steht für “sich gegenseitig ausschließen.”

In diesem Fall trifft dies auf unser Beispiel zu, da der Bridge-Operator der einzige ist, der die Kontrolle über unser Semaphor und den Mutex-Up/Down-Handle hat. Wenn das Wachhaus am Ende der Brücke dagegen einen Brückenüberbrückungsschalter hätte, hätten wir immer noch ein Semaphor-Setup, aber keinen Mutex.

Beide Konstrukte werden regelmäßig in der Computerprogrammierung verwendet, wenn mehrere Threads verwendet werden, um sicherzustellen, dass zu keiner Zeit nur ein einzelner Prozess oder eine Aufgabe auf eine bestimmte Ressource zugreifen kann. Einige Semaphoren können ultraschnell schalten, zum Beispiel wenn sie in Multithread-Finanzmarkt-Handelssoftware verwendet werden, und andere können viel langsamer sein und nur alle paar Minuten ihren Zustand ändern, wie wenn sie in einer automatisierten Zugbrücke oder in einer Straßenzugüberquerung verwendet werden.< /p>

Da wir nun ein besseres Verständnis von Semaphoren haben, implementieren wir eines in Bash.

Implementieren eines Semaphors in Bash: Einfach, oder nicht?

Die Implementierung eines Semaphors in Bash ist so einfach, dass es sogar direkt von der Befehlszeile aus erfolgen kann, oder so scheint es.…

Lassen Sie es’ s einfach anfangen.

BRIDGE=up if [ “${BRIDGE}” = “down” ]; dann echo “Autos dürfen vorbeifahren!”; else echo “Schiffe dürfen passieren!”; fi BRIDGE=down if [ “${BRIDGE}” = “down” ]; dann echo “Autos dürfen vorbeifahren!”; else echo “Schiffe dürfen passieren!”; fi

Werbung

In diesem Code hält die Variable BRIDGE unsere Brücke Status. Wenn wir es aufstellen, können Schiffe passieren, und wenn wir es absetzen, können Autos vorbeifahren. Wir könnten auch jederzeit den Wert unserer Variablen auslesen, um zu sehen, ob die Brücke wirklich oben oder unten ist. Die geteilte/gemeinsame Ressource ist in diesem Fall unsere Brücke.

Dieses Beispiel ist jedoch Single-Threaded, und daher sind wir nie in eine emaphore-erforderliche Situation geraten. Eine andere Möglichkeit, dies zu bedenken, ist, dass unsere Variable niemals genau zum selben Zeitpunkt auf und ab sein kann, wenn der Code sequentiell, dh Schritt für Schritt, ausgeführt wird.

Eine andere Sache, die wir beachten sollten, ist, dass wir es getan haben den Zugriff auf eine andere Variable nicht wirklich kontrollieren (wie es ein Semaphor normalerweise tun würde), und daher ist unsere BRIDGE-Variable nicht wirklich eine echte Semaphor-Variable, obwohl sie nahe kommt.

Schließlich, sobald wir mehrere Threads einführen, die die BRIDGE-Variable beeinflussen können, stoßen wir auf Probleme. Was ist beispielsweise, wenn direkt nach dem BRIDGE=up-Befehl ein anderer Thread BRIDGE=down ausgibt, was dann zu der Meldung Cars may pass! führen würde! ausgegeben, obwohl der erste Thread erwarten würde, dass die Brücke aktiv ist, und in Wirklichkeit bewegt sich die Brücke noch. Gefährlich!

Sie können sehen, wie schnell die Dinge unübersichtlich und unübersichtlich werden können, ganz zu schweigen von der Komplexität, wenn Sie mit mehreren Threads arbeiten.

Werbung

Die Situation, in der mehrere Threads versuchen, dieselbe Variable entweder gleichzeitig oder zumindest rechtzeitig zu aktualisieren, damit ein anderer Thread die Situation falsch versteht (was im Fall von Zugbrücken eine ganze Weile dauern kann) wird als a . bezeichnet Race-Bedingung: Zwei Threads rennen um die Aktualisierung oder melden eine Variable oder einen Status, mit dem Ergebnis, dass ein oder mehrere Threads möglicherweise ziemlich falsch liegen.

Wir können diesen Code viel besser machen, indem wir den Code in Prozeduren einfließen lassen und eine echte Semaphor-Variable verwenden, die den Zugriff auf unsere BRIDGE-Variable je nach Situation einschränkt.

Erstellen eines Bash-Semaphors

Die Implementierung eines vollständigen Multithreads, der Thread-sicher ist (ein Computerbegriff zur Beschreibung von Software, die Thread-sicher ist oder so entwickelt wurde, dass sich Threads nicht negativ/falsch beeinflussen können, wenn sie dies nicht sollten) ist keine leichte Aufgabe. Selbst ein gut geschriebenes Programm, das Semaphoren verwendet, ist nicht garantiert, dass es vollständig Thread-sicher ist.

Je mehr Threads es gibt und je höher die Häufigkeit und Komplexität der Thread-Interaktionen, desto wahrscheinlicher ist dies werden Rennbedingungen sein.

In unserem kleinen Beispiel betrachten wir die Definition eines Bash-Semaphors, wenn einer der Zugbrücken-Bediener einen Brückengriff senkt und damit anzeigt, dass er oder sie die Brücke senken möchte. Eifrige Leser haben vielleicht den Hinweis auf Operators anstelle von Operator bemerkt: Es gibt jetzt mehrere Operatoren, die die Brücke absenken können. Mit anderen Worten, es gibt mehrere Threads oder Aufgaben, die alle gleichzeitig ausgeführt werden.

#!/bin/bash BRIDGE_SEMAPHORE=0 lower_bridge(){ # Ein Operator hat einen der Bridge-Operations-Handles nach unten gesetzt (als neuen Zustand). # Angenommen, es wurde zuvor zwischen den Operatoren vereinbart, dass, sobald einer der Operatoren # einen Bridge-Operation-Handle bewegt, sein Befehl entweder früher oder später ausgeführt werden muss # daher beginnen wir eine Schleife, die darauf wartet, dass die Bridge verfügbar wird für Bewegung, während es wahr ist; tun, wenn [ “${BRIDGE_SEMAPHORE}” -eq 1 ]; dann echo “Brückensemaphor gesperrt, Brücke bewegt oder ein anderes Problem. Warten Sie 2 Minuten, bevor Sie die Überprüfung wiederholen.” sleep 120 weiter # Schleife fortsetzen elif [ “${BRIDGE_SEMAPHORE}” -eq 0 ]; dann echo “Befehl Brücke absenken akzeptiert, Semaphor sperren und Brücke absenken.” BRIDGE_SEMAPHORE=1 execute_lower_bridge wait_for_bridge_to_come_down BRIDGE='down' echo “Brücke abgesenkt, so dass mindestens 5 Minuten vor der nächsten erlaubten Brückenbewegung vergehen.” sleep 300 echo “5 Minuten vergangen, Semaphore entsperren (Brückensteuerung freigeben)” BRIDGE_SEMAPHORE=0 break # Schleife beenden fi done }

Hier haben wir eine lower_bridge-Funktion, die eine Reihe von Dingen erledigt. Nehmen wir zunächst an, ein anderer Betreiber hat die Brücke vor kurzem innerhalb der letzten Minute nach oben verlegt. Daher gibt es einen anderen Thread, der Code in einer ähnlichen Funktion namens raise_bridge ausführt.

Werbung

Tatsächlich hat diese Funktion das Hochfahren der Brücke beendet, aber eine obligatorische 5-Minuten-Wartezeit eingeführt, auf die sich alle Betreiber zuvor geeinigt hatten und die im Quellcode fest codiert war: Sie verhindert das Hochfahren der Brücke/die ganze Zeit runter. Sie können diese obligatorische 5-Minuten-Wartezeit auch in dieser Funktion als Sleep 300 sehen.

Wenn also die Funktion raise_bridge ausgeführt wird, hat sie die Semaphor-Variable BRIDGE_SEMAPHORE auf 1 gesetzt, genau wie wir es hier im Code tun (direkt nach dem Echo “Lower bridge command Accepted, Locking Semaphore and Lowering Bridge”) und & #8211; mittels der ersten if-bedingten Prüfung in diesem Code – Die in dieser Funktion vorhandene Endlosschleife wird mit einer Pause von 2 Minuten fortgesetzt (ref Weiter im Code), da die Variable BRIDGE_SEMAPHORE 1 ist.

Sobald die Funktion raise_bridge das Hochfahren der Bridge und den fünfminütigen Ruhezustand beendet, setzt sie BRIDGE_SEMAPHORE auf 0, sodass unsere Lower_bridge-Funktion mit der Ausführung der Funktionen execute_lower_bridge und anschließend wait_for_bridge_to_come_down beginnen kann, während sie zuerst unser Semaphor wieder auf 1 gesperrt hat, um dies zu verhindern andere Funktionen davon ab, die Brückensteuerung zu übernehmen.

Es gibt jedoch Mängel in diesem Code, und Race-Conditions, die weitreichende Konsequenzen für Brückenbetreiber haben können, sind möglich. Kannst du welche entdecken?

Der “Lower bridge Befehl akzeptiert, Semaphore sperren und Brücke senken” ist nicht threadsicher!

Wenn ein anderer Thread, zum Beispiel raise_bridge, gleichzeitig ausgeführt wird und versucht, auf die Variable BRIDGE_SEMAPHORE zuzugreifen, könnte dies sein (wenn BRIDGE_SEMAPHORE=0 und beide ausgeführten Threads ihre jeweiligen Echos genau zur gleichen Zeit erreichen, zu der die Bridge-Operatoren “Bridge-Befehl akzeptiert, Semaphor sperren und Bridge senken” und “Befehl Brücke heben akzeptiert, Semaphor sperren und Brücke heben”. Direkt hintereinander auf dem Bildschirm! Beängstigend, nicht wahr?

Werbung

Noch beängstigender ist die Tatsache, dass beide Threads mit BRIDGE_SEMAPHORE=1 fortfahren können und beide Threads weiterhin ausgeführt werden können! (Nichts hindert sie daran) Der Grund dafür ist, dass es für solche Szenarien noch nicht viel Schutz gibt. Dieser Code implementiert zwar ein Semaphor, ist aber keineswegs threadsicher. Wie bereits erwähnt, ist Multithread-Codierung komplex und erfordert viel Fachwissen.

Obwohl das erforderliche Timing in diesem Fall minimal ist (1-2 Codezeilen benötigen nur wenige Millisekunden, um ausgeführt zu werden) und angesichts der wahrscheinlich geringen Anzahl von Bridge-Operatoren ist die Möglichkeit, dass dies geschieht, sehr gering. Die Tatsache, dass es möglich ist, macht es jedoch gefährlich. Thread-sicheren Code in Bash zu erstellen ist keine leichte Aufgabe.

Dies könnte weiter verbessert werden, indem zum Beispiel eine Vorsperre eingeführt wird und/oder eine Verzögerung mit anschließender erneuter Überprüfung eingeführt wird (obwohl dies wahrscheinlich eine zusätzliche Variable erfordert) oder eine regelmäßige erneute Überprüfung vor der tatsächlichen Brückenausführung durchgeführt wird , usw. Eine andere Möglichkeit besteht darin, eine Prioritätswarteschlange oder eine Zählervariable zu erstellen, die überprüft, wie viele Threads die Kontrolle über die Brücke gesperrt haben usw.

Ein weiterer häufig verwendeter Ansatz, z. B. beim Ausführen mehrerer Bash-Skripte, die interagieren, besteht darin, mkdir oder flock als Basis-Sperroperationen zu verwenden. Es gibt verschiedene Beispiele dafür, wie diese online implementiert werden können, z. B. Welche Unix-Befehle können als Semaphor/Sperre verwendet werden?

Zusammenfassung< /h2>

In diesem Artikel sehen wir uns an, was ein Semaphor ist. Auch das Thema Mutex haben wir kurz angesprochen. Schließlich haben wir uns die Implementierung eines Semaphors in Bash am praktischen Beispiel von mehreren Brückenbetreibern angesehen, die eine bewegliche Brücke/Zugbrücke betreiben. Wir haben auch untersucht, wie komplex die Implementierung einer zuverlässigen Semaphor-basierten Lösung ist.

Wenn Ihnen das Lesen dieses Artikels gefallen hat, werfen Sie einen Blick auf unsere Asserts, Errors and Crashes: Was ist der Unterschied? Artikel.

Exit mobile version