Programmiertechnik/Dateien sperren/in Perl

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Perl bietet analog zu PHP eine Funktion flock an, die ebenso wie die PHP-Funktion entweder die Betriebssystemfunktion flock(3) verwendet oder – falls erstere nicht vorhanden ist oder dies beim Erstellen von Perl explizit gewünscht wird – die Funktion fcntl(2). Unter Windows wird hier ebenso wie bei PHP die Funktion LockFileEx genutzt. Die Funktion besitzt die folgende Signatur:

Beispiel
flock FILEHANDLE, OPERATION

Allgemeines

Um die Funktion sinnvoll nutzen zu können, sollte das Fcntl-Paket eingebunden sein, zumindest der flock-Teil:

Beispiel
use Fcntl ':flock';

Damit stehen dann die Konstanten LOCK_EX, LOCK_SH, LOCK_UN und LOCK_NB zur Verfügung. Zwar werden diese Konstanten zu Zahlen wie 1, 2, etc. ausgewertet, allerdings ist es sinnvoller, die Konstanten zu nutzen und nicht die Zahlen fest in den Code zu schreiben. Zum einen ist dies viel lesbarer (LOCK_EX versteht man eher als 2) und zum anderen ist es so möglich, dass von einer Perl-Version auf die nächste theoretisch die Konstanten geändert werden könnten (aus welchen Gründen auch immer), ohne, dass dies Auswirkungen auf den tatsächlichen Code hätte.

Die Funktion erwartet die folgenden Parameter:

Parameter Erläuterung
FILEHANDLE Der Dateideskriptor der Datei, die zu sperren ist (wie er mit open geöffnet werden kann)
OPERATION Welche Operation durchzuführen ist (sperren, entsperren) – siehe unten


Die Funktion gibt im Erfolgsfall true und im Fehlerfall false zurück. Folgende Operationen sind möglich:

Operation Erläuterung
LOCK_EX Sperrt die Datei exklusiv (zum Schreiben geeignet). Diese Sperre kann nur dann etabliert werden, wenn keine andere Sperre (welcher Art auch immer) auf der Datei vorhanden ist, daher wartet die Funktion, bis alle aktuellen Sperren freigeworden sind. Während diese Sperre aktiv ist, kann keine andere Sperre auf die Datei etabliert werden.
LOCK_SH Sperrt die Datei mitbenutzbar (zum Lesen geeignet). Diese Sperre kann nur etabliert werden, wenn keine bisherige Sperre des Typs LOCK_EX vorhanden ist – allerdings kann sie problemlos mit weiteren Sperren des Typs LOCK_SH koexistieren.
LOCK_UN Entsperrt die Datei.
LOCK_NB LOCK_NB oder LOCK_SH | LOCK_NB.

Eine Datei auslesen

Eine Datei auslesen
#!/usr/bin/env perl

use Fcntl ':flock';
use strict;
use warnings;

open DATEI, '<', 'counter.txt' or die "Konnte die Datei nicht öffnen: $!";
flock DATEI, LOCK_SH or die "Sperren der Datei fehlgeschlagen: $!";
my $aktuellerZaehler = <DATEI>;
close DATEI;

print "Aktueller Zählerstand: $aktuellerZaehler\n";

Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt auslesen kann. Zuerst wird die Datei zum Lesen geöffnet ('<') geöffnet, damit man einen Dateideskriptor erhält. Die erste Aktion nach dem Öffnen der Datei ist das Sperren derselben. Da hier kein LOCK_NB angegeben wurde, kehrt flock erst dann zurück, sobald es möglich ist, eine Sperre zu errichten, d. h. eine eventuelle, alte Sperre entfernt wurde. Falls es aus irgend einem Grund nicht möglich ist, die Datei zu sperren, gibt flock false zurück. Da hier die Datei ausschließlich gelesen werden soll, wird hier LOCK_SH verwendet. Nach dem Sperren wird die Datei ausgelesen, danach wird die Datei geschlossen und damit die Sperre automatisch aufgehoben.


Ausschließliches Beschreiben einer Datei (1)

Ausschließliches Beschreiben einer Datei (1)
#!/usr/bin/env perl

use Fcntl ':flock';
use strict;
use warnings;

open DATEI, '>>', 'counter.txt' or die "Konnte die Datei nicht öffnen: $!";
close DATEI;

open DATEI, '+<', 'counter.txt' or die "Konnte die Datei nicht öffnen: $!";
flock DATEI, LOCK_EX or die "Sperren der Datei fehlgeschlagen: $!";
truncate DATEI, 0;
print DATEI "0";
close DATEI;

print "Zähler wurde zurückgesetzt.\n";

Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt neu schreiben kann, wenn man vorher kein Interesse am vorherigen Inhalt hat. Dazu wird die Datei im Lese-Schreib-Modus geöffnet. Hier wird der Modus '+<' gewählt. Dies ist wichtig, damit es zwar Möglich ist, auf die Datei zu schreiben, der Dateiinhalt beim Öffnen jedoch nicht zerstört wird, da zu dem Zeitpunkt die Datei noch nicht gesperrt werden kann. Dies würde nämlich bei '+>' oder '>' geschehen. Danach wird eine exklusive Sperre errichtet und nun erst wird der Dateiinhalt gelöscht (über truncate) und danach neu beschrieben. Bisher unerwähnt ist das Öffnen der Datei im Anhängemodus ('>>') am Anfang. Dies sorgt dafür, dass die Datei existiert. Dies ist wichtig, denn beim Öffnen einer Datei über den normalen Modus '>' würde die Datei im Falle der Nichtexistenz angelegt werden – etwas, das das Öffnen per '+<' nicht bewerkstelligt, dies würde fehlschlagen, falls die Datei nicht existiert. Wenn man sich jedoch sicher ist, dass die Datei bereits existiert, kann man das Öffnen und Schließen der Datei im Anhängemodus selbstverständlich auslassen.

Streng genommen besteht zwischen den beiden Aufrufen von open auch eine race condition – allerdings nur im Falle, dass ein weiteres Programm die Datei ohne weiteres löschen könnte. Da dies aber in Verbindung mit Dateisperren sowieso nicht portabel realisierbar ist, stellt dies keine wirklich bedeutende Einschränkung dar.


Ausschließliches Beschreiben einer Datei (2)

Ausschließliches Beschreiben einer Datei (2)
#!/usr/bin/env perl

use Fcntl ':flock';
use strict;
use warnings;

open DATEI, '>>', 'counter.txt' or die "Konnte die Datei nicht öffnen: $!";
flock DATEI, LOCK_EX or die "Sperren der Datei fehlgeschlagen: $!";
truncate DATEI, 0;
print DATEI "0";
close DATEI;

print "Zähler wurde zurückgesetzt.\n";

Dieses Beispiel gleicht dem obigen Beispiel – allerdings wird in diesem Fall die Datei im Anhänge-Modus ('>>') geöffnet. Dies führt im Normalfall dazu, dass jeder Inhalt, den man schreibt, immer an das Ende der Datei angehängt wird, egal, ob man die Dateiposition per seek oder ähnlichem verstellt. Allerdings führt hier das truncate dazu, dass das Dateiende wieder bei Byte 0 ist, nachdem die Funktion den kompletten Dateiinhalt löscht. Damit wird das gleiche Verhalten, wie bei der anderen Variante mit '+<' und dem vorherigen eventuellen Anlegen der Datei erzielt, jedoch tritt hier die oben beschriebene race condition nicht auf – mit dem Nachteil, dass im Gegensatz zu '+<' während des Schreibvorgangs ein Umpositionieren des Positionszeigers der Datei ohne Effekt ist.


Lesen und Schreiben in eine Datei

Lesen und Schreiben in eine Datei
#!/usr/bin/env perl

use Fcntl ':flock';
use strict;
use warnings;

open DATEI, '+<', 'counter.txt' or die "Konnte die Datei nicht öffnen: $!";
flock DATEI, LOCK_EX or die "Sperren der Datei fehlgeschlagen: $!";
my $aktuellerZaehler = int (<DATEI>);
$aktuellerZaehler += 1;
seek DATEI, 0, 0;
truncate DATEI, 0;
print DATEI "$aktuellerZaehler";
close DATEI;

print "Zähler wurde um eins erhöht und beträgt jetzt $aktuellerZaehler.\n";

In diesem Beispiel geht es darum, eine Datei zuerst auszulesen, auf den Inhalt zu reagieren und den Inhalt danach wieder in die Datei zurückzuschreiben. Wichtig ist hierbei, dass die Datei über den Verlauf der gesamten Operation exklusiv gesperrt bleibt, damit keine race conditions auftreten. Die hier dargestellte Methode ist eine Abwandlung der ersten Methode zum Schreiben der Datei. Allerdings wird hier vor dem Löschen des Dateiinhalts die Datei zuerst gelesen. Die Zeile mit dem seek-Funktionsaufruf ist notwendig, um den Positionszeiger der Datei nach dem Lesen wieder an den Anfang zu setzen.


Erkennen, ob flock zur Verfügung steht

Da flock das Perl-Script mit einer Fehlermeldung beendet, falls es auf dem Betriebssystem nicht zur Verfügung steht, ist es für das Script sinnvoller, vorher abzufragen, ob flock überhaupt funktionieren wird, denn die grundsätzliche Funktion des Programms mit eventuellen Problemen auf Grund von fehlenden Dateisperren kann unter Umständen wichtiger sein, als eine hunderprozentig garantierte Datenkonsistenz.

Um abzufragen, ob flock zur Verfügung steht, kann das Config-Modul verwendet werden. Folgender Code gibt aus, ob Dateisperren auf dem jeweiligen Betriebssystem zur Verfügung stehen, oder nicht.

Beispiel
use Config;

if ((!$Config{'d_flock'} || $Config{'d_flock'} ne 'define') &&
    (!$Config{'d_fcntl_can_lock'} || $Config{'d_fcntl_can_lock'} ne 'define')) {
  print "Sperren sind nicht verfügbar!\n";
} else {
  print "Sperren sind verfügbar!\n";
}

Hierbei werden die beiden Konfigurationsparameter d_flock und d_fcntl_can_lock abgefragt – wenn eine von beiden gesetzt ist, steht flock zur Verfügung.

Weblinks