Christian Seiler: datei beim auslesen sperren? und wie dann?

Beitrag lesen

Hallo,

welche werte soll ich dann flock geben? lesen und schreiben
ich kenne mich nicht aus und will nichts falsch machen mit dem befehl flock.

Locking von Dateien ist eine sehr trickreiche Sache. Die Funktion flock() bietet 3 mögliche Operationen an:

* LOCK_SH: Ein shared lock auf die Datei anlegen. Es können beliebig viele
            Prozesse ein shared lock auf eine Datei anlegen - ein shared
            lock verhindert *lediglich*, dass ein exclusive lock auf eine
            Datei angelegt wird. Ferner kann man kein shared lock anlegen,
            falls die Datei gerade mit einem exclusive lock gelockt ist
            (wenn etwas nicht geht, wartet flock() immer, bis die Sperre
            wieder frei ist)
 * LOCK_EX: Ein exclusive lock auf die Datei anlegen. Es kann nur ein
            exclusive lock auf eine Datei angelegt werden.
 * LOCK_UN: Alle Locks des *aktuelllen* Prozesses entfernen. Im Prinzip
            unnötig, wenn Du die Datei direkt nach dem Lesen/Schreiben
            wieder schließen willst.

Wie wäre also die Vorgehensweise für das Locking von Dateien?

* Lesende Prozesse öffnen die Datei mit fopen im 'r'-Modus und versuchen
   dann mit LOCK_SH die Datei zu sperren. Sobald flock erfolgreich war,
   können sie sich sicher sein, dass kein anderer Prozess die Datei
   verändert. Da bei fclose() auch alle Locks entfernt werden, ist es
   *nicht* notwendig, die Sperre zu entfernen, wenn man fclose () verwendet.
 * Schreibende Prozesss öffnen die Datei (dazu später mehr) und versuchen
   dann mit LOCK_EX die Datei zu sperren. Sobald flock erfolgreich war,
   können sie sich sicher sein, dass die Datei *nur* für sie geöffent ist
   und kein anderer Prozess dazwischenfunkt. LOCK_EX sollte man nur so kurz
   wie möglich verwenden, denn solange sind andere Prozesse gesperrt.

Dabei gibt es jedoch noch einige *sehr* wichtige Punkt zu beachten:

* flock() funktioniert nicht auf FAT-Dateisystemen, nicht unter Windows
   95/98/ME (unter NT4/2000/XP/2003/Vista schon) und nicht über die meisten
   Netzwerkdateisysteme.
 * Insbesondere unter Windows, wenn PHP als ISAP-Filter betrieben wird,
   aber unter Umständen auch unter anderen Betriebsystemen, wenn PHP als
   Multithread-Webservermodul verwendet wird, kann es sein, dass flock()
   nutzlos ist, denn flock() ist in manchen Betriebsystemen nur auf
   Prozess-, nicht aber auf Thread-Ebene implementiert. Wenn PHP als CGI
   oder als Webservermodul eines Singlethread-Webservers läuft, dann
   besteht das Problem nicht.
 * flock() ist eine *freiwillige* Angelegenheit. Ich kann alles mit der
   Datei anstellen, auch wenn sie von einem anderen Prozess per LOCK_EX
   gesperrt ist. D.h. alle Programme, die auf die Datei zugreifen, *müssen*
   Locking korrekt implementieren, damit es zu keinen Fehlern kommt, d.h.
   sie *müssen* sich an die Konvention halten.

Und als _allerwichtigsten_ Punkt kommt noch das Öffnen von Dateien zum Schreiben (!) hinzu. Betrachten wir nun folgenden Beispielcode, der eine Datei zum *Lesen* öffnet:

$fp = fopen("datei", "r");  
if (flock($fp, LOCK_SH)) {  
   // irgendwas aus $fp auslesen  
} else {  
   echo "Fehler beim Anfordern der Sperre!";  
}  
fclose($fp);

Der Code ist so korrekt, d.h. wenn sich alle lesenden Prozesse an das obige Schema halten, dann funktioniert's korrekt. Betrachten wir jedoch mal folgenden Beispielcode, der eine Datei zum Schreiben öffnet.

$fp = fopen("datei", "w");  
if (flock($fp, LOCK_EX)) {  
   // irgendwas in $fp schreiben  
} else {  
   echo "Fehler beim Anfordern der Sperre!";  
}  
fclose($fp);

Sieht doch ganz OK aus, oder? FALSCH!

Das Problem an dem obigen Code ist, dass der Code Änderung an der Datei vornimmt, *bevor* das Lock angefordert wurde. Warum das? Da ist doch ne if-Bedingung? Die Antwort: Beim fopen () mit dem "w"-Flag wird der komplette Inhalt der Datei gelöscht (d.h. wenn Du nur fclose (fopen ("datei", "w")); machst, dann löscht Du den kompletten Inhalt der Datei "datei" - oder legst eine Datei mit diesem Namen an).

Oftmals wird hierfür eine Lösung über eine separate Lockfile vorgeschlagen. Obwohl das durchaus funktioniert, ist das nicht die einfachste:

Es gibt zwei sich sehr ähnliche Lösungsmöglichkeiten für das Problem:

1. Die Datei als 'r+' öffnen (statt als 'w') - dann wird die Datei zum Lesen geöffent mit zusätzlicher Möglichkeit, in der Datei zu schreiben - die Datei wird allerdings *nicht* beim Öffnen gelöscht. Nach dem Erlangen der Sperre wird die Datei per ftruncate() gelöscht und dann kann normal darin geschrieben werden:

$fp = fopen("datei", "r+");  
if (flock($fp, LOCK_EX)) {  
   // Kompletten Dateiinhalt löschen  
   ftruncate ($fp, 0);  
   // irgendwas in $fp schreiben  
} else {  
   echo "Fehler beim Anfordern der Sperre!";  
}  
fclose($fp);

Dieses Verfahren hat einen gravierenden Nachteil: Wenn die Datei nicht existiert, schlägt das Öffnen der Datei fehl. Man kann dem aber abhelfen, indem man mit der touch()-Funktion vorher dafür sorgt, dass die Datei existiert, d.h. *vor* dem fopen() noch ein

touch ($datei);

Das sorgt dafür, dass die Datei definitiv existiert, bevor man sie öffnet, allerdings fasst touch() den Inhalt der Datei nicht an.

2. Die alternative Lösung ist, die Datei zum Anhängen zu öffnen ('a' statt 'r+') und dann wieder ftruncate() anzuwenden:

$fp = fopen("datei", "a");  
if (flock($fp, LOCK_EX)) {  
   // Kompletten Dateiinhalt löschen  
   ftruncate ($fp, 0);  
   // irgendwas in $fp schreiben  
} else {  
   echo "Fehler beim Anfordern der Sperre!";  
}  
fclose($fp);

Diese Lösung hat den Vorteil, dass fopen() beim Öffnen über 'a' die Datei im Zweifel auch anlegt. *Allerdings* hat dieses Verfahren den Nachteil, dass man weder im 'a'- noch im 'a+'-Modus in der Datei mittels fseek() die Position verändern kann. Ok, im 'a+'-Modus geht's, um irgendwo aus der Datei etwas zu *lesen* - allerdings wird in beiden 'a'-Modi alles, was geschrieben wird, *immer* an die Datei angehöngt, *nicht* an die aktuelle Position geschrieben - nur das ftruncate() sorgt dafür, es überhaupt wieder am Anfang der Datei losgeht (wenn die Datei leer ist, dann heißt "anhängen" einfach "ab dem Anfang schreiben"). Wenn Du also *nur* linear Schreiben willst und fseek() gar nicht zum Verändern der SCHREIB-Position verwenden willst, dann ist die 'a'-Lösung genauso gut wie die 'r+'-Lösung - wenn Du allerdings mittels fseek() die SCHREIB-Position verändern willst, dann ist die 'a'-Lösung keine Option und Du musst die 'r+'-Lösung verwenden.

Viele Grüße,
Christian

--
"I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup