Programmiertechnik/Dateien sperren/in Python
Python ist zwar eine plattformunabhängige Sprache, allerdings sind die Module, mit denen man Datei-Locking implementieren kann, stark plattformabhängig. Es gibt in der Standard-Python-API keinen Weg, Dateien sowohl unter Windows als auch unter UNIX-artigen Betriebssystemen mit dem gleichen Aufruf zu sperren – man muss je nach Betriebssystem unterschiedliche Methoden verwenden.
Inhaltsverzeichnis
Sperren unter UNIX-artigen Betriebsystemen
Zudem gibt es unter UNIX-artigen Betriebssystemen zwei Funktionen, die dies bewerkstelligen: fcntl.flock
und fcntl.lockf
. fcntl.flock
ist eine Schnittstelle zu flock(2)
– und falls diese Funktion nicht existiert zu fcntl(2)
. fcntl.lockf
ist entgegen des Namens keine Schnittstelle zu lockf(3)
, sondern zu fcntl(2)
. Im Folgenden wird aus Konsistenzgründen mit der PHP- und Perl-Variante die fcntl.flock
-Methode besprochen. Unter UNIX-artigen Betriebssystemen muss also das fcntl-Modul eingebunden werden:
import fnctl
Die Funktion fcntl.flock besitzt folgende Signatur:
fcntl.flock (fd, operation)
Die Funktion erwartet die folgenden Parameter:
Parameter | Erläuterung |
---|---|
fd |
Ein Dateiobjekt, wie es von open zurückgeliefert wird. |
operation |
Welche Operation durchzuführen ist (sperren, entsperren) – siehe unten |
Die Funktion wirft im Fehlerfall eine Exception vom Typ IOError
. Folgende Operationen sind möglich:
Parameter | Erläuterung |
---|---|
fcntl.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. |
fcntl.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.
|
fcntl.LOCK_UN |
Entsperrt die Datei. |
fcntl.LOCK_NB |
LOCK_NB oder LOCK_SH | LOCK_NB .
|
Sperren unter Microsoft Windows
Unter Windows bietet Python die Funktion msvcrt.locking
an, die eine Schnittstelle zur Visual C++ Runtime-Bibliothek ist, die selbst wiederum LockFileEx
nutzt. Obwohl LockFileEx
selbst durchaus sowohl mitbenutzbare als auch exklusive Sperren unterstützt, bietet msvcrt.locking
nur exklusive Sperren an. Unter Windows muss also das Modul msvcrt
eingebunden werden:
import msvcrt
Die Funktion msvcrt.locking
besitzt folgende Signatur:
msvcrt.locking (fd, mode, nbytes)
Die Funktion erwartet die folgenden Parameter:
Parameter | Erläuterung |
---|---|
fd |
Ein Dateideskriptor, wie er durch die Methode fileno() eines Dateiobjekts erhalten werden kann.
|
mode |
Welche Operation durchzuführen ist (sperren, entsperren) – siehe unten |
nbytes |
Wie viele Bytes der Datei gesperrt werden sollen. Die Sperre beginnt hierbei immer an der aktuellen Dateizeigerposition, dieser Wert gibt lediglich die Länge des zu sperrenden Bereichs an. Es ist daher sinnvoll – sofern die Datei nicht direkt vorher geöffnet wurde – vor einem Aufruf dieser Funktion den Positionszeiger auf den Dateianfang zurückzusetzen (mittels seek ). Die Begrenzung der Sperre darf über die tatsächliche Größe der Datei hinausgehen, es empfiehlt sich, einen großen Wert wie 2147483647L (2 GB) anzugeben, damit (im Normalfall) die komplette Datei unabhängig von ihrer tatsächlichen Größe gesperrt ist.
|
Die Funktion wirft im Fehlerfall eine Exception vom Typ IOError. Folgende Operationen sind möglich:
Parameter | Erläuterung |
---|---|
msvcrt.LK_LOCK msvcrt.LK_RLCK |
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. |
msvcrt.LK_NBLCK msvcrt.LK_NBRLCK |
Sperrt die Datei exklusiv (zum Schreiben geeignet) und kehrt sofort zurück, falls die Datei nicht gesperrt werden kann |
msvcrt.LK_UNLCK |
Entsperrt die Datei. |
Im folgenden sind alle Beispiele durch bedingte Anweisungen so geschrieben, dass sie sowohl unter Windows als auch unter POSIX-artigen Betriebssystemen funktionieren.
Eine Datei auslesen
#!/usr/bin/env python
import os
if os.name == 'nt':
import msvcrt
else:
import fcntl
datei = open ('counter.txt', 'r')
if os.name == 'nt':
msvcrt.locking (datei.fileno (), msvcrt.LK_LOCK, 2147483647L)
else:
fcntl.flock (datei, fcntl.LOCK_SH)
zaehler = datei.read ()
datei.close ()
print "Aktueller Zaehlerstand: " + zaehler
Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt auslesen kann. Zuerst wird die Datei zum Lesen geöffnet ('r')
geöffnet, damit man einen Dateideskriptor erhält. Die erste Aktion nach dem Öffnen der Datei ist das Sperren derselben. Hierbei wird für Windows und UNIX-artige Betriebssysteme unterschieden und die jeweils passende Methode genutzt. Da hier kein fcntl.LOCK_NB / msvcrt.LK_NBLCK
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. Da hier die Datei ausschließlich gelesen werden soll, wird hier unter UNIX-artigen Betriebssystemen fcntl.LOCK_SH
verwendet (unter Windows kann hier leider nur exklusiv gesperrt werden). Nach dem Sperren wird die Datei ausgelesen, danach wird die Datei geschlossen und damit die Sperre automatisch aufgehoben.
Ausschließliches Beschreiben einer Datei (1)
#!/usr/bin/env python
import os
if os.name == 'nt':
import msvcrt
else:
import fcntl
datei = open ('counter.txt', 'a')
datei.close ()
datei = open ('counter.txt', 'r+')
if os.name == 'nt':
msvcrt.locking (datei.fileno (), msvcrt.LK_LOCK, 2147483647L)
else:
fcntl.flock (datei, fcntl.LOCK_EX)
datei.truncate (0)
datei.write ('0')
datei.close ()
print "Zaehler wurde zurueckgesetzt."
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 'r+' 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 bei 'w+' oder 'w' 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 ('a') am Anfang. Dies sorgt dafür, dass die Datei existiert. Dies ist wichtig, denn beim Öffnen einer Datei über den normalen Modus 'w' würde die Datei im Falle der Nichtexistenz angelegt werden – etwas, das das Öffnen per 'r+' 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)
#!/usr/bin/env python
import os
if os.name == 'nt':
import msvcrt
else:
import fcntl
datei = open ('counter.txt', 'a')
if os.name == 'nt':
datei.seek (0)
msvcrt.locking (datei.fileno (), msvcrt.LK_LOCK, 2147483647L)
else:
fcntl.flock (datei, fcntl.LOCK_EX)
datei.truncate (0)
datei.write ('0')
datei.close ()
print "Zaehler wurde zurueckgesetzt."
Dieses Beispiel gleicht dem obigen Beispiel – allerdings wird in diesem Fall die Datei im Anhänge-Modus ('a')
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 'r+'
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 'r+'
während des Schreibvorgangs ein Umpositionieren des Positionszeigers der Datei ohne Effekt ist.
Lesen und Schreiben in eine Datei
#!/usr/bin/env python
import os
if os.name == 'nt':
import msvcrt
else:
import fcntl
datei = open ('counter.txt', 'r+')
if os.name == 'nt':
msvcrt.locking (datei.fileno (), msvcrt.LK_LOCK, 2147483647L)
else:
fcntl.flock (datei, fcntl.LOCK_EX)
zaehler = int (datei.read ()) + 1
datei.seek (0)
datei.truncate (0)
datei.write (str (zaehler))
datei.close ()
print "Zaehler wurde um eins erhoeht und betraegt jetzt " + str (zaehler)
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.
Weblinks
- Python-Dokumentation: fcntl
- Python-Dokumentation: msvcrt-Modul (Abschnitt Dateien)