Hallo zusammen,
Ich habe hier ein altes Perlscript, dass im Grund nichts anderes macht, als sich einen freien Index aus einer Datenbank zu suchen, damit einen Datenbankeintrag erstellt und den Index zurückmeldet. Das funktioniert solange, wie das Script nicht zweimal parallel gestartet wird. Das zweite Script nimmt dann den gleichen freien Index und versucht damit erfolglos den Datenbankeintrag zu erstellen (Index schon belegt), so dass die darauf aufbauenenden Prozesse auf Fehler laufen, klassische Racecondition also.
Wie verhindere ich eine parallele Ausführung?
Ich habe überlegt, solange zu warten, bis das Script nur einmal als Prozess läuft. Wenn allerdings beide Script zeitgleich gestartet werden und dann gegenseitig warten, bis sie "fertig" werden, funktioniert das auch nicht.
Ideen dazu?
Perl: Script soll warten, wenn es bereits läuft
- heisenberg
- Beiträge: 4132
- Registriert: 04.06.2015 01:17:27
- Lizenz eigener Beiträge: MIT Lizenz
Re: Perl: Script soll warten, wenn es bereits läuft
Parallele Ausführung verhindert man programmiersprachenübergreifend z. B. per Locking. Z. B. per File-Locking oder zu Deutsch Dateisperren. Es gibt aber noch andere Arten des Lockings.
Dateisperren kurz erklärt:
Du suchst dir einen Dateinamen aus. Z. B. /run/lock/deinprog/deinprog.lock. [1] Am Anfang des Programmes versucht, das Programm eine Sperre (=Lock) auf diese Datei zu erhalten. Eine Sperre kann - grob gesagt - immer nur ein Programm bekommen. Wenn das erhalten dieser Sperre fehlschlägt, beendet es sich wieder. Wenn es die Sperre erhält, dann führt sich das Programm aus und am Ende des Programmes wird die Sperre wieder freigegeben.
Bzgl. Locking gibt es noch zwei Varianten bzgl. der Art, wie die Sperre angefragt wird: Blockierend oder Nicht-Blockierend. Blockierend bedeutet, dass eine Sperre angefordert wird und dabei wird gewartet, bis die Sperre verfügbar ist. Nicht-Blockierend bedeutet, dass der Aufruf sofort beendet wird, entweder erfolglos ( Sperre nicht verfügbar) oder erfolgreich (Sperre erhalten).
Bei Perl gibt's hier u. a. die flock - Funktion:
https://perldoc.perl.org/functions/flock
Siehe auch:
https://de.wikipedia.org/wiki/Lock
[1] Der Filesystem Hierarchy Standard legt den Ablageort solcher Dateien auf den Pfad unterhalb von /run/lock fest.
Dateisperren kurz erklärt:
Du suchst dir einen Dateinamen aus. Z. B. /run/lock/deinprog/deinprog.lock. [1] Am Anfang des Programmes versucht, das Programm eine Sperre (=Lock) auf diese Datei zu erhalten. Eine Sperre kann - grob gesagt - immer nur ein Programm bekommen. Wenn das erhalten dieser Sperre fehlschlägt, beendet es sich wieder. Wenn es die Sperre erhält, dann führt sich das Programm aus und am Ende des Programmes wird die Sperre wieder freigegeben.
Bzgl. Locking gibt es noch zwei Varianten bzgl. der Art, wie die Sperre angefragt wird: Blockierend oder Nicht-Blockierend. Blockierend bedeutet, dass eine Sperre angefordert wird und dabei wird gewartet, bis die Sperre verfügbar ist. Nicht-Blockierend bedeutet, dass der Aufruf sofort beendet wird, entweder erfolglos ( Sperre nicht verfügbar) oder erfolgreich (Sperre erhalten).
Bei Perl gibt's hier u. a. die flock - Funktion:
https://perldoc.perl.org/functions/flock
Siehe auch:
https://de.wikipedia.org/wiki/Lock
[1] Der Filesystem Hierarchy Standard legt den Ablageort solcher Dateien auf den Pfad unterhalb von /run/lock fest.
Re: Perl: Script soll warten, wenn es bereits läuft
Wenn die simple und primitive Methode genügt: einfach im Skript beim Start eine Datei anlegen bzw. wenn die Datei existiert, das Skript mit einer Fehlermeldung beenden. Beim regulären Beenden des Skripts die Datei wieder entfernen. Wenn das Skript abgebrochen wurde, die Datei von Hand entfernen.
Re: Perl: Script soll warten, wenn es bereits läuft
Teste am Anfang des Scripts, ob eine „Lock-Datei“ vorhanden ist. Wenn die Datei vorhanden ist, beendest Du – wenn nicht, erstellst Du sie (z.B. touch).
Als Shellscript etwa so:
Als Shellscript etwa so:
Code: Alles auswählen
gszaktilla@lili:~/test$ cat bla.sh
#!/bin/sh
if [ -e /var/lock/bla-lock ]; then
echo "Wird schon ausgefuehrt. Ende."
exit
else
touch /var/lock/bla-lock
sleep 10 # oder tu sonstwas
rm /var/lock/bla-lock
fi
gszaktilla@lili:~/test$ ./bla.sh &
[1] 19905
gszaktilla@lili:~/test$ ./bla.sh
Wird schon ausgefuehrt. Ende.
gszaktilla@lili:~/test$
Wenn man keine Probleme hat, kann man sich welche machen. ("Großes Lötauge", Medizinmann der M3-Hopi [und sog. Maker])
Re: Perl: Script soll warten, wenn es bereits läuft
Bei Bedenken, was passieren könnte, wenn das Skript zweimal gleichzeitig gestartet wird und beide Instanzen noch keine Lock-Datei vorfinden, könnte man auch so vorgehen: die Datei erzeugen und seine eigene Prozess-ID hineinschreiben. Anschliessend wieder einlesen. Wenn die Prozess-ID die eigene ist, weitermachen, wenn nicht, mit einer Fehlermeldung abbrechen.
Re: Perl: Script soll warten, wenn es bereits läuft
Als Ergänzung der Beträge darüber sollte man das Entfernen eines Locks in einem Trap-Pendant (zur Shell) ausführen, um auch auf Abbrüche reagieren (den Lock entfernen) zu können:
https://www.perl.com/article/37/2013/8/ ... s-in-Perl/
https://www.perl.com/article/37/2013/8/ ... s-in-Perl/
- heisenberg
- Beiträge: 4132
- Registriert: 04.06.2015 01:17:27
- Lizenz eigener Beiträge: MIT Lizenz
Re: Perl: Script soll warten, wenn es bereits läuft
Ich habe (auch) lange gedacht: Locking? Für das bisschen Shellscripting brauch ich das nicht! Ich mache mir das einfach und für das bisschen, was ich tue reicht das schon. Leider musste ich öfters erleben, wie mir Dinge Ärger gemacht haben, weil das dann doch gebrochen ist.
Ein simples Beispiel, dass die Methode "flock" und "lock per touch" testet. Das ist jetzt zur Darstellung übertrieben, aber nichts desto trotz wird es im laufenden Betrieb vermutlich irgendwann vorkommen und wenn es vorkommt, dann kotzt man im Strahl, weil man vermutlich mit einem Fehler konfrontiert ist, der vielleicht gar nicht mal so einfach zu finden ist. Also empfehle ich, dass lieber gleich sauber zu machen - vor allem, wenn da schon eine Datenbank im Spiel ist. Und vom Aufwand macht es auch keinen Unterschied. Man muss sich nur kurz die Zeit nehmen, um das einmal gemacht zu haben.
Es soll ein Script, dass parallel aufgerufen wird, 100 Mal ausgeführt werden, um in einer Datei einen Zähler um 1 zu erhöhen. Dabei muss sichergestellt sein, dass jede einzelne Erhöhung geschrieben wird. Der Zähler ist vorher 0 und sollte also danach 100 sein.
Ich habe 3 Bash-Scripte geschrieben:
Man sieht direkt, dass das einfache Lock mit touch für Parallelität absolut ungeeignet ist. Das ist im Endeffekt eher ein Zufallszahlengenerator als eine verlässliche Ausführungssteuerung. Die flock-Variante läuft in jedem Fall einwandfrei.
Nachdem im Vorlagescript "locking mit touch", das Locking als nicht-blockierende Variante war, habe ich das mit flock hier genauso gemacht. Damit es aber einfacher ist, würde ich hier `blockierend` bevorzugen.
Ein simples Beispiel, dass die Methode "flock" und "lock per touch" testet. Das ist jetzt zur Darstellung übertrieben, aber nichts desto trotz wird es im laufenden Betrieb vermutlich irgendwann vorkommen und wenn es vorkommt, dann kotzt man im Strahl, weil man vermutlich mit einem Fehler konfrontiert ist, der vielleicht gar nicht mal so einfach zu finden ist. Also empfehle ich, dass lieber gleich sauber zu machen - vor allem, wenn da schon eine Datenbank im Spiel ist. Und vom Aufwand macht es auch keinen Unterschied. Man muss sich nur kurz die Zeit nehmen, um das einmal gemacht zu haben.
Es soll ein Script, dass parallel aufgerufen wird, 100 Mal ausgeführt werden, um in einer Datei einen Zähler um 1 zu erhöhen. Dabei muss sichergestellt sein, dass jede einzelne Erhöhung geschrieben wird. Der Zähler ist vorher 0 und sollte also danach 100 sein.
Ich habe 3 Bash-Scripte geschrieben:
- Ein Script, dass das mit "lock per touch" macht (42304)
- Ein Script, dass das mit "flock" tut (42303)
- Ein Testscript, dass mehrere Tests durchführt (42302):
- 100 mal sequentiell ausführen für beide lock-scripte
- 100 mal parallel ausführen für beide lock-scripte
Code: Alles auswählen
$ ./test_program
Data Value after 100 sequential executions with simple lock: 100
Data Value after 100 sequential executions with flock: 100
Data Value after 100 parallel executions with simple lock: 9
Data Value after 100 parallel executions with flock 100
$ ./test_program
Data Value after 100 sequential executions with simple lock: 100
Data Value after 100 sequential executions with flock: 100
Data Value after 100 parallel executions with simple lock: 44
Data Value after 100 parallel executions with flock 100
$ ./test_program
Data Value after 100 sequential executions with simple lock: 100
Data Value after 100 sequential executions with flock: 100
Data Value after 100 parallel executions with simple lock: 4
Data Value after 100 parallel executions with flock 100
$ ./test_program
Data Value after 100 sequential executions with simple lock: 100
Data Value after 100 sequential executions with flock: 100
Data Value after 100 parallel executions with simple lock: 9
Data Value after 100 parallel executions with flock 100
$ ./test_program
Data Value after 100 sequential executions with simple lock: 100
Data Value after 100 sequential executions with flock: 100
Data Value after 100 parallel executions with simple lock: 4
Data Value after 100 parallel executions with flock 100
Nachdem im Vorlagescript "locking mit touch", das Locking als nicht-blockierende Variante war, habe ich das mit flock hier genauso gemacht. Damit es aber einfacher ist, würde ich hier `blockierend` bevorzugen.