Hoe maak je een semafoor in Bash

0
192

Bent u bezig met het ontwikkelen van een toepassing met meerdere threads? Vroeg of laat zul je waarschijnlijk een semafoor moeten gebruiken. In dit artikel leer je wat een semafoor is, hoe je er een maakt/implementeert in Bash, en meer.

Wat is een Semaphore ?

Een semafoor is een programmeerconstructie die wordt gebruikt in computerprogramma's die gebruikmaken van meerdere verwerkingsthreads (computerverwerkingsthreads die elk de broncode van hetzelfde programma of dezelfde reeks programma's uitvoeren) om exclusief gebruik van een gemeenschappelijke hulpbron op een bepaald moment. Op een veel gemakkelijkere manier gezegd, denk hier eens over na als “één tegelijk, alstublieft”.

Een semafoor werd voor het eerst gedefinieerd tegen het einde van de jaren zestig door wijlen computerwetenschapper Edsger Dijkstra uit Rotterdam in Nederland. Waarschijnlijk heb je in je leven vaak semaforen gebruikt zonder dat je je er specifiek van bewust was dat je dat deed!

Een tijdje in Nederland blijven, een land vol kleine waterwegen en veel beweegbare bruggen (een ophaalbrug genoemd in Amerikaans Engels), kan men veel uitstekende voorbeelden uit de echte wereld van een semafoor zien; denk aan het handvat van een ophaalbrugoperator: omhoog of omlaag. Die handgreep is de seinpaalvariabele die de waterweg of de weg beschermt tegen ongevallen. Zo kunnen de waterweg en wegen worden gezien als andere variabelen die worden beschermd door de semafoor.

Als de hendel omhoog staat en de brug open is, wordt het exclusieve gebruik van de kruising water/weg gegeven aan het schip of schepen die door het waterkanaal varen. Wanneer de hendel naar beneden is, is de brug gesloten en wordt het exclusieve gebruik van het water/wegkruispunt gegeven aan de auto's die over de brug rijden.

Advertentie

De semafoorvariabele kan de toegang tot een andere set variabelen regelen. De status omhoog van de handgreep voorkomt bijvoorbeeld dat de variabelen $cars_per_minute en $car_toll_booth_total_income worden bijgewerkt, enz.

We kunnen het voorbeeld wat verder nemen en verduidelijken dat wanneer de handgreep omhoog of omlaag gaat, een passend licht is zichtbaar voor zowel scheepskapiteins op het water als voor mensen die in auto's en vrachtwagens op de weg rijden: alle operators kunnen een gemeenschappelijke variabele voor een specifieke staat aflezen.

Terwijl het hier beschreven scenario niet alleen een semafoor is, maar ook een simpele mutex. Een mutex is een andere veel voorkomende programmeerconstructie die erg lijkt op een semafoor, met als extra voorwaarde dat een mutex alleen kan worden ontgrendeld door dezelfde taak of thread die hem heeft vergrendeld. Mutex staat voor “wederzijds exclusief.”

In dit geval is dit van toepassing op ons voorbeeld, aangezien de brugwachter de enige is die controle heeft over onze semafoor en mutex omhoog/omlaag-hendel. Als het wachthuis aan het einde van de brug daarentegen een overbruggingsschakelaar voor de brug had, zouden we nog steeds een semafooropstelling hebben, maar geen mutex.

Beide constructies worden regelmatig gebruikt bij het programmeren van computers wanneer meerdere threads worden gebruikt om ervoor te zorgen dat slechts een enkel proces of taak op elk moment toegang heeft tot een bepaalde bron. Sommige semaforen kunnen ultrasnel schakelen, bijvoorbeeld wanneer ze worden gebruikt in multithreaded financiële markthandelssoftware, en sommige kunnen veel langzamer zijn en slechts om de paar minuten van status veranderen, zoals wanneer ze worden gebruikt in een geautomatiseerde ophaalbrug of in een spoorwegovergang.< /p>

Nu we een beter begrip hebben van semaforen, laten we er een in Bash implementeren.

Een semafoor implementeren in Bash: Makkelijk of niet?

Het implementeren van een semafoor in Bash is zo eenvoudig dat het zelfs rechtstreeks vanaf de opdrachtregel kan worden gedaan, althans zo lijkt het…

Let’ s begin simpel.

BRIDGE=omhoog als [ “${BRIDGE}” = “omlaag” ]; echo dan “Auto's mogen passeren!”; else echo “Schepen mogen passeren!”; fi BRIDGE=omlaag if [ “${BRIDGE}” = “omlaag” ]; echo dan “Auto's mogen passeren!”; else echo “Schepen mogen passeren!”; fi

Advertentie

In deze code houdt de variabele BRIDGE onze brug toestand. Als we hem omhoog zetten, kunnen schepen passeren en als we hem omlaag zetten, kunnen auto's passeren. We kunnen ook op elk moment de waarde van onze variabele uitlezen om te zien of de brug echt omhoog of omlaag is. De gedeelde/gemeenschappelijke bron is in dit geval onze brug.

Dit voorbeeld is echter single-threaded, en dus zijn we nooit een semafoor vereist situatie tegengekomen. Een andere manier om hierover na te denken, is dat onze variabele nooit op hetzelfde moment op en neer kan gaan, omdat de code sequentieel wordt uitgevoerd, dat wil zeggen stap voor stap.

Een ander ding om op te merken is dat we dat wel deden niet echt de toegang tot een andere variabele controleren (zoals een semafoor gewoonlijk zou doen), en dus is onze BRIDGE-variabele niet echt een echte semafoorvariabele, hoewel het in de buurt komt.

Tot slot, zodra we meerdere threads introduceren die de BRIDGE-variabele kunnen beïnvloeden, komen we problemen tegen. Bijvoorbeeld, wat als, direct na het BRIDGE=up commando, een andere thread BRIDGE=down geeft, wat dan zou resulteren in het bericht Cars may pass! output, ook al zou de eerste thread verwachten dat de brug omhoog is, en in werkelijkheid is de brug nog steeds in beweging. Gevaarlijk!

Je kunt zien hoe dingen snel troebel en verwarrend kunnen worden, om nog maar te zwijgen van ingewikkeld, wanneer je met meerdere threads werkt.

Advertentie

De situatie waarin meerdere threads proberen dezelfde variabele op hetzelfde moment bij te werken of in ieder geval zo dichtbij dat een andere thread de situatie verkeerd kan krijgen (wat in het geval van ophaalbruggen een behoorlijke tijd kan duren) wordt een race-conditie: twee threads racen om een ​​variabele of status bij te werken of te rapporteren, met als resultaat dat een of meer threads het helemaal mis kunnen hebben.

We kunnen deze code veel beter maken door de code in procedures te laten lopen en door een echte semafoorvariabele te gebruiken die de toegang tot onze BRIDGE-variabele zal beperken, afhankelijk van de situatie.

Een bash-semafoor maken

Het implementeren van een volledige multi-threaded, die thread-safe is (een computerterm om software te beschrijven die thread-safe is of zodanig is ontwikkeld dat threads elkaar niet negatief/onjuist kunnen beïnvloeden wanneer dat niet zou moeten) is geen sinecure. Zelfs een goed geschreven programma dat semaforen gebruikt, is niet gegarandeerd volledig thread-safe.

Hoe meer threads er zijn, en hoe hoger de frequentie en complexiteit van thread-interacties, hoe waarschijnlijker het is dat er zullen racecondities zijn.

Voor ons kleine voorbeeld zullen we kijken naar het definiëren van een Bash-semafoor wanneer een van de ophaalbrugoperators een brughendel laat zakken, en daarmee aangeeft dat hij of zij de brug wil laten zakken. Fanatieke lezers hebben misschien de verwijzing naar operators opgemerkt in plaats van operator: er zijn nu meerdere operators die de brug kunnen laten zakken. Met andere woorden, er zijn meerdere threads of taken die allemaal tegelijkertijd worden uitgevoerd.

#!/bin/bash BRIDGE_SEMAPHORE=0 lower_bridge(){ # Een operator heeft een van de bedieningshendels van de brug naar beneden geplaatst (als een nieuwe status). # Neem aan dat eerder tussen operators is overeengekomen dat zodra een van de operators # een bridge-operatiehandvat verplaatst dat hun commando vroeg of laat moet worden uitgevoerd # daarom beginnen we een lus die zal wachten tot de bridge beschikbaar is voor beweging terwijl het waar is; doen als [ “${BRIDGE_SEMAPHORE}” -eq 1 ]; echo dan “Brug semafoor vergrendeld, brug beweegt of ander probleem. Wacht 2 minuten voordat u opnieuw controleert.” sleep 120 doorgaan # Continue loop elif [ “${BRIDGE_SEMAPHORE}” -eq 0]; dan echo “Laat brug commando geaccepteerd, semafoor vergrendelen en de brug laten zakken.” BRIDGE_SEMAPHORE=1 execute_lower_bridge wait_for_bridge_to_come_down BRIDGE='down' echo “Brug verlaagd, zorg ervoor dat er minstens 5 minuten verstrijken voor de volgende toegestane brugbeweging.” sleep 300 echo “5 minuten verstreken, semafoor ontgrendeld (brugbesturing vrijgeven)” BRIDGE_SEMAPHORE=0 break # Exit loop fi done }

Hier hebben we een lower_bridge functie die een aantal dingen zal doen. Laten we eerst aannemen dat een andere operator de brug onlangs in de laatste minuut heeft verplaatst. Als zodanig is er een andere thread die code uitvoert in een functie die lijkt op deze genaamd raise_bridge.

Advertentie

In feite is die functie klaar met het optrekken van de brug, maar heeft een verplichte wachttijd van 5 minuten ingesteld waar alle operators eerder mee instemden en die hard gecodeerd was in de broncode: het voorkomt dat de brug omhoog gaat/de hele tijd naar beneden. U kunt deze verplichte wachttijd van 5 minuten ook zien geïmplementeerd in deze functie als slaap 300.

Dus, wanneer die raise_bridge-functie actief is, zal deze de semafoorvariabele BRIDGE_SEMAPHORE op 1 hebben gezet, net zoals we doen in de code hier (direct na de echo “Lower bridge command geaccepteerd, locking semafoor en verlaging van bridge” commando), en & #8211; door middel van de eerste indien voorwaardelijke controle in deze code – de oneindige lus die aanwezig is in deze functie zal doorgaan (ref verder in de code) om te lussen, met pauzes van 2 minuten, aangezien de BRIDGE_SEMAPHORE variabele 1 is.

Zodra die raise_bridge-functie klaar is met het verhogen van de brug en het beëindigen van zijn vijf minuten slaap, zal het de BRIDGE_SEMAPHORE op 0 zetten, waardoor onze lower_bridge-functie co kan beginnen met het uitvoeren van de functies execute_lower_bridge en vervolgens wait_for_bridge_to_come_down terwijl we eerst onze semafoor opnieuw op 1 hebben vergrendeld om te voorkomen dat andere functies van het overnemen van de brugbesturing.

Er zijn echter tekortkomingen in deze code en racecondities die verstrekkende gevolgen kunnen hebben voor brugbeheerders zijn mogelijk. Zie jij er een?

Het “Lower bridge commando geaccepteerd, locking semafoor en verlaging brug” is niet thread-safe!

Als een andere thread, bijvoorbeeld raise_bridge tegelijkertijd wordt uitgevoerd en probeert toegang te krijgen tot de BRIDGE_SEMAPHORE variabele, kan dit zijn (wanneer BRIDGE_SEMAPHORE=0 en beide actieve threads bereiken hun respectieve echo‘s op exact hetzelfde moment dat de brugwachters zien dat “Bevel neerlaten geaccepteerd, semafoor vergrendelen en brug neerlaten” en “Opdracht brug opheffen geaccepteerd, semafoor vergrendelen en brug opheffen”. Direct na elkaar op het scherm! Eng, niet?

Advertentie

Enger nog is het feit dat beide threads kunnen doorgaan naar BRIDGE_SEMAPHORE=1, en beide threads kunnen doorgaan met uitvoeren! (Er is niets dat hen daarvan weerhoudt) De reden is dat er nog niet veel bescherming is voor dergelijke scenario's. Hoewel deze code een semafoor implementeert, is deze dus zeker niet thread-safe. Zoals gezegd is multi-threaded codering complex en vereist veel expertise.

Hoewel de vereiste timing in dit geval minimaal is (1-2 regels code nemen slechts enkele milliseconden in beslag), en gezien het waarschijnlijk lage aantal bridge-operators, is de kans dat dit gebeurt erg klein. Het feit dat het mogelijk is, maakt het echter gevaarlijk. Het creëren van thread-safe code in Bash is geen gemakkelijke opgave.

Dit kan verder worden verbeterd door bijvoorbeeld een pre-lock in te voeren en/of door een vorm van vertraging in te voeren bij de daaropvolgende hercontrole (hoewel dit waarschijnlijk een extra variabele vereist) of door een regelmatige hercontrole uit te voeren voordat de daadwerkelijke bruguitvoering wordt uitgevoerd , enz. Een andere optie is om een ​​prioriteitswachtrij of een tellervariabele te maken die controleert hoeveel threads de controle over de brug hebben vergrendeld enz.

Een andere veelgebruikte benadering, bijvoorbeeld bij het uitvoeren van meerdere bash-scripts die interactie, is om mkdir of flock te gebruiken als basisvergrendelingsoperaties. Er zijn verschillende voorbeelden van hoe u deze online kunt implementeren, bijvoorbeeld: Welke Unix-commando's kunnen worden gebruikt als een semafoor/slot?.

Afronding< /h2>

In dit artikel bekijken we wat een semafoor is. We hebben ook kort het onderwerp van een mutex aangeroerd. Ten slotte hebben we gekeken naar het implementeren van een semafoor in Bash aan de hand van het praktijkvoorbeeld van meerdere brugwachters die een beweegbare brug/ophaalbrug bedienen. We hebben ook onderzocht hoe complex het implementeren van een betrouwbare op semafoor gebaseerde oplossing is.

Als je dit artikel met plezier hebt gelezen, bekijk dan onze beweringen, fouten en crashes: wat is het verschil? artikel.