Programmiertechnik/Dateien sperren/in Python

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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.

Sperren unter UNIX-artigen Betriebsystemen[Bearbeiten]

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:

Beispiel
import fnctl

Die Funktion fcntl.flock besitzt folgende Signatur:

Beispiel
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[Bearbeiten]

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[Bearbeiten]

Beispiel: 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.

Beachten Sie: In diesem Code ist keine Fehlerbehandlung vorhanden, da alle Funktionen unter Python Exceptions werfen, die bei diesen trivialen Beispielen zum sofortigen Ende des Programms führen, was hier auch gewünscht ist (vgl. die Perl/PHP-Beispiele). In anderen Zusammenhängen sollten die Exceptions jedoch sinnvoll abgefangen werden!


Ausschließliches Beschreiben einer Datei (1)[Bearbeiten]

Beispiel: 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)[Bearbeiten]

Beispiel: 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[Bearbeiten]

Beispiel: 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[Bearbeiten]