Sperren einer Datei in Python

152

Ich muss eine Datei zum Schreiben in Python sperren. Es wird von mehreren Python-Prozessen gleichzeitig darauf zugegriffen. Ich habe einige Lösungen online gefunden, aber die meisten scheitern für meine Zwecke, da sie oft nur auf Unix oder Windows basieren.

Evan Fosmark
quelle

Antworten:

115

Okay, also bin ich zu dem Code gekommen, den ich hier geschrieben habe. Der Link auf meiner Website ist tot. Siehe archive.org ( auch auf GitHub verfügbar ). Ich kann es folgendermaßen verwenden:

from filelock import FileLock

with FileLock("myfile.txt.lock"):
    print("Lock acquired.")
    with open("myfile.txt"):
        # work with the file as it is now locked
Evan Fosmark
quelle
10
Wie aus einem Kommentar im Blog-Beitrag hervorgeht, ist diese Lösung nicht "perfekt", da das Programm so beendet werden kann, dass die Sperre bestehen bleibt und Sie die Sperre vor der Datei manuell löschen müssen wird wieder zugänglich. Davon abgesehen ist dies jedoch immer noch eine gute Lösung.
LeetNightshade
3
Eine weitere verbesserte Version von Evans FileLock finden Sie hier: github.com/ilastik/lazyflow/blob/master/lazyflow/utility/…
Stuart Berg
3
OpenStack hat eine eigene Implementierung (nun, Skip Montanaro) veröffentlicht - pylockfile - Sehr ähnlich zu den in früheren Kommentaren erwähnten, aber dennoch einen Blick wert.
Jweyrich
7
@jweyrich Openstacks pylockfile ist jetzt veraltet. Es wird empfohlen, stattdessen Verbindungselemente oder oslo.concurrency zu verwenden.
Harbun
2
Eine andere ähnliche Implementierung, denke ich: github.com/benediktschmitt/py-filelock
herry
39

Hier gibt es ein plattformübergreifendes Dateisperrmodul: Portalocker

Obwohl, wie Kevin sagt, das Schreiben in eine Datei aus mehreren Prozessen gleichzeitig vermieden werden sollte, wenn dies überhaupt möglich ist.

Wenn Sie Ihr Problem in eine Datenbank einbinden können, können Sie SQLite verwenden. Es unterstützt den gleichzeitigen Zugriff und verwaltet seine eigenen Sperren.

John Fouhy
quelle
16
+1 - SQLite ist in solchen Situationen fast immer der richtige Weg.
CDLeary
2
Portalocker benötigt dafür Python Extensions für Windows.
n611x007
2
@naxa gibt es eine Variante davon, die nur auf msvcrt und ctypes basiert, siehe roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…
Shmil The Cat
@ n611x007 Portalocker wurde gerade aktualisiert, so dass unter Windows keine Erweiterungen mehr erforderlich sind :)
Wolph
2
SQLite unterstützt gleichzeitigen Zugriff?
Piotr
23

Die anderen Lösungen zitieren viele externe Codebasen. Wenn Sie es lieber selbst tun möchten, finden Sie hier einen Code für eine plattformübergreifende Lösung, die die entsprechenden Tools zum Sperren von Dateien auf Linux / DOS-Systemen verwendet.

try:
    # Posix based file locking (Linux, Ubuntu, MacOS, etc.)
    import fcntl, os
    def lock_file(f):
        fcntl.lockf(f, fcntl.LOCK_EX)
    def unlock_file(f):
        fcntl.lockf(f, fcntl.LOCK_UN)
except ModuleNotFoundError:
    # Windows file locking
    import msvcrt, os
    def file_size(f):
        return os.path.getsize( os.path.realpath(f.name) )
    def lock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f))
    def unlock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f))


# Class for ensuring that all file operations are atomic, treat
# initialization like a standard call to 'open' that happens to be atomic.
# This file opener *must* be used in a "with" block.
class AtomicOpen:
    # Open the file with arguments provided by user. Then acquire
    # a lock on that file object (WARNING: Advisory locking).
    def __init__(self, path, *args, **kwargs):
        # Open the file and acquire a lock on the file before operating
        self.file = open(path,*args, **kwargs)
        # Lock the opened file
        lock_file(self.file)

    # Return the opened file object (knowing a lock has been obtained).
    def __enter__(self, *args, **kwargs): return self.file

    # Unlock the file and close the file object.
    def __exit__(self, exc_type=None, exc_value=None, traceback=None):        
        # Flush to make sure all buffered contents are written to file.
        self.file.flush()
        os.fsync(self.file.fileno())
        # Release the lock on the file.
        unlock_file(self.file)
        self.file.close()
        # Handle exceptions that may have come up during execution, by
        # default any exceptions are raised to the user.
        if (exc_type != None): return False
        else:                  return True        

Nun AtomicOpenkann in einen verwendet werden , withBlock , in dem man normalerweise eine Verwendung openAussage.

WARNUNG: Wenn die Ausführung unter Windows und Python vor dem Aufruf von exit abstürzt , bin ich mir nicht sicher, wie das Sperrverhalten aussehen würde.

WARNUNG: Die hier bereitgestellte Verriegelung ist nicht absolut, sondern nur eine Empfehlung. Alle potenziell konkurrierenden Prozesse müssen die Klasse "AtomicOpen" verwenden.

Thomas Lux
quelle
unlock_fileDatei unter Linux sollte nicht fcntlwieder mit dem LOCK_UNFlag aufrufen ?
eadmaster
Das Entsperren erfolgt automatisch, wenn das Dateiobjekt geschlossen wird. Es war jedoch eine schlechte Programmierpraxis von mir, sie nicht aufzunehmen. Ich habe den Code aktualisiert und den fcntl-Entsperrvorgang hinzugefügt!
Thomas Lux
In __exit__dir closeaußerhalb des Schlosses nach unlock_file. Ich glaube, die Laufzeit könnte Daten während spülen (dh schreiben) close. Ich glaube, man muss flushund fsyncunter dem Schloss, um sicherzustellen, dass während des Schlosses keine zusätzlichen Daten geschrieben werden close.
Benjamin Bannier
Danke für die Korrektur! Ich überprüfte , dass es ist die Möglichkeit für eine Race - Bedingung , ohne die flushund fsync. Ich habe die beiden Zeilen hinzugefügt, die Sie vor dem Anruf vorgeschlagen haben unlock. Ich habe erneut getestet und die Rennbedingung scheint gelöst zu sein.
Thomas Lux
1
Das einzige, was "schief" geht, ist, dass bis der Prozess 1 die Datei sperrt, ihr Inhalt abgeschnitten wird (Inhalt gelöscht). Sie können dies selbst testen, indem Sie dem obigen Code vor dem Sperren eine weitere Datei "open" mit einem "w" hinzufügen . Dies ist jedoch unvermeidlich, da Sie die Datei öffnen müssen, bevor Sie sie sperren können. Zur Verdeutlichung bedeutet "atomar", dass in einer Datei nur legitime Dateiinhalte gefunden werden. Dies bedeutet, dass Sie niemals eine Datei mit Inhalten aus mehreren konkurrierenden Prozessen erhalten, die miteinander vermischt sind.
Thomas Lux
15

Ich bevorzuge lockfile - Plattformunabhängiges Sperren von Dateien

Ferrdo
quelle
3
Diese Bibliothek scheint gut geschrieben zu sein, aber es gibt keinen Mechanismus zum Erkennen veralteter Sperrdateien. Es verfolgt die PID, die die Sperre erstellt hat, und sollte daher erkennbar sein, ob dieser Prozess noch ausgeführt wird.
Sherbang
1
@sherbang: Was ist mit remove_existing_pidfile ?
Janus Troelsen
@JanusTroelsen Das Pidlockfile-Modul erfasst keine atomaren Sperren.
Sherbang
@herbang Bist du sicher? Es öffnet die Sperrdatei im Modus O_CREAT | O_EXCL.
Mhsmith
2
Bitte beachten Sie, dass diese Bibliothek ersetzt wurde und Teil von github.com/harlowja/fasteners
congusbongus
13

Ich habe nach verschiedenen Lösungen gesucht, um dies zu erreichen, und meine Wahl fiel auf oslo.concurrency

Es ist mächtig und relativ gut dokumentiert. Es basiert auf Verbindungselementen.

Andere Lösungen:

Maxime Viargues
quelle
Betreff: Portalocker, Sie können pywin32 jetzt über pip über das Paket pypiwin32 installieren.
Timothy Jannace
13

Das Sperren ist plattform- und gerätespezifisch, aber im Allgemeinen haben Sie einige Optionen:

  1. Verwenden Sie flock () oder ein gleichwertiges Produkt (wenn Ihr Betriebssystem dies unterstützt). Dies ist eine empfohlene Sperre, es sei denn, Sie prüfen, ob die Sperre ignoriert wird.
  2. Verwenden Sie eine Lock-Copy-Move-Unlock-Methode, bei der Sie die Datei kopieren, die neuen Daten schreiben und dann verschieben (Verschieben, nicht Kopieren - Verschieben ist eine atomare Operation unter Linux - Überprüfen Sie Ihr Betriebssystem) und nach dem suchen Existenz der Sperrdatei.
  3. Verwenden Sie ein Verzeichnis als "Sperre". Dies ist erforderlich, wenn Sie in NFS schreiben, da NFS flock () nicht unterstützt.
  4. Es gibt auch die Möglichkeit, gemeinsam genutzten Speicher zwischen den Prozessen zu verwenden, aber das habe ich noch nie versucht. Es ist sehr betriebssystemspezifisch.

Für all diese Methoden müssen Sie eine Spin-Lock-Technik (Retry-After-Failure) verwenden, um die Sperre zu erwerben und zu testen. Dies lässt zwar ein kleines Fenster für die Fehlersynchronisation, ist jedoch im Allgemeinen klein genug, um kein großes Problem zu sein.

Wenn Sie nach einer plattformübergreifenden Lösung suchen, sollten Sie sich besser über einen anderen Mechanismus bei einem anderen System anmelden (das nächstbeste ist die oben beschriebene NFS-Technik).

Beachten Sie, dass SQLite gegenüber NFS denselben Einschränkungen unterliegt wie normale Dateien. Sie können also nicht in eine SQLite-Datenbank auf einer Netzwerkfreigabe schreiben und die Synchronisierung kostenlos durchführen.

Richard Levasseur
quelle
4
Hinweis: Verschieben / Umbenennen ist in Win32 nicht atomar. Referenz: stackoverflow.com/questions/167414/…
sherbang
4
Neuer Hinweis: os.renameist jetzt in Win32 seit Python 3.3 atomar
Ghostkeeper
7

Die Koordinierung des Zugriffs auf eine einzelne Datei auf Betriebssystemebene ist mit allen möglichen Problemen behaftet, die Sie wahrscheinlich nicht lösen möchten.

Am besten haben Sie einen separaten Prozess, der den Lese- / Schreibzugriff auf diese Datei koordiniert.

Kevin
quelle
19
"separater Prozess, der den Lese- / Schreibzugriff auf diese Datei koordiniert" - mit anderen Worten, implementieren Sie einen Datenbankserver :-)
Eli Bendersky
1
Dies ist eigentlich die beste Antwort. Nur zu sagen, "Datenbankserver verwenden" ist zu stark vereinfacht, da eine Datenbank nicht immer das richtige Werkzeug für den Job ist. Was ist, wenn es sich um eine reine Textdatei handeln muss? Eine gute Lösung könnte darin bestehen, einen untergeordneten Prozess zu erzeugen und dann über eine Named Pipe, einen Unix-Socket oder einen gemeinsam genutzten Speicher darauf zuzugreifen.
Brendon Crawford
9
-1 weil dies nur FUD ohne Erklärung ist. Das Sperren einer Datei zum Schreiben scheint mir ein ziemlich einfaches Konzept zu sein, das Betriebssysteme mit ähnlichen Funktionen anbieten flock. Ein Ansatz, "Ihre eigenen Mutexe und einen Daemon-Prozess zu rollen, um sie zu verwalten", scheint ein ziemlich extremer und komplizierter Ansatz zu sein, um ihn zu lösen ... ein Problem, von dem Sie uns nicht wirklich erzählt haben, das aber nur beängstigend vorgeschlagen wurde.
Mark Amery
-1 aus den von @Mark Amery angegebenen Gründen sowie für eine unbegründete Stellungnahme zu den Problemen, die das OP lösen möchte
Michael Krebs
5

Das Sperren einer Datei ist normalerweise eine plattformspezifische Operation. Daher müssen Sie möglicherweise die Möglichkeit berücksichtigen, auf verschiedenen Betriebssystemen ausgeführt zu werden. Beispielsweise:

import os

def my_lock(f):
    if os.name == "posix":
        # Unix or OS X specific locking here
    elif os.name == "nt":
        # Windows specific locking here
    else:
        print "Unknown operating system, lock unavailable"
Greg Hewgill
quelle
7
Möglicherweise wissen Sie dies bereits, aber das Plattformmodul ist auch verfügbar, um Informationen zur laufenden Plattform abzurufen. platform.system (). docs.python.org/library/platform.html .
Mönch
2

Ich habe an einer solchen Situation gearbeitet, in der ich mehrere Kopien desselben Programms aus demselben Verzeichnis / Ordner und Protokollierungsfehlern ausführe. Mein Ansatz war es, vor dem Öffnen der Protokolldatei eine "Sperrdatei" auf die Disc zu schreiben. Das Programm prüft vor dem Fortfahren, ob die "Sperrdatei" vorhanden ist, und wartet, bis es an der Reihe ist, wenn die "Sperrdatei" vorhanden ist.

Hier ist der Code:

def errlogger(error):

    while True:
        if not exists('errloglock'):
            lock = open('errloglock', 'w')
            if exists('errorlog'): log = open('errorlog', 'a')
            else: log = open('errorlog', 'w')
            log.write(str(datetime.utcnow())[0:-7] + ' ' + error + '\n')
            log.close()
            remove('errloglock')
            return
        else:
            check = stat('errloglock')
            if time() - check.st_ctime > 0.01: remove('errloglock')
            print('waiting my turn')

BEARBEITEN --- Nachdem ich über einige der obigen Kommentare zu veralteten Sperren nachgedacht hatte, bearbeitete ich den Code, um eine Überprüfung auf Veraltetheit der "Sperrdatei" hinzuzufügen. Das Timing mehrerer tausend Iterationen dieser Funktion auf meinem System ergab einen Durchschnitt von 0,002066 ... Sekunden von kurz zuvor:

lock = open('errloglock', 'w')

zu kurz danach:

remove('errloglock')

Also dachte ich mir, ich werde mit dem Fünffachen dieses Betrags beginnen, um die Alterung anzuzeigen und die Situation auf Probleme zu überwachen.

Als ich mit dem Timing arbeitete, stellte ich fest, dass ich ein bisschen Code hatte, der nicht wirklich notwendig war:

lock.close()

was ich unmittelbar nach der offenen Anweisung hatte, also habe ich es in dieser Bearbeitung entfernt.

weißer Bart
quelle
2

Um die Antwort von Evan Fossmark zu ergänzen , finden Sie hier ein Beispiel für die Verwendung von Filelock :

from filelock import FileLock

lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
    file = open(path, "w")
    file.write("123")
    file.close()

Jeder Code innerhalb des with lock:Blocks ist threadsicher. Dies bedeutet, dass er beendet wird, bevor ein anderer Prozess Zugriff auf die Datei hat.

Josh Correia
quelle
1

Das Szenario sieht folgendermaßen aus: Der Benutzer fordert eine Datei auf, etwas zu tun. Wenn der Benutzer dieselbe Anforderung erneut sendet, informiert er den Benutzer darüber, dass die zweite Anforderung erst ausgeführt wird, wenn die erste Anforderung abgeschlossen ist. Deshalb benutze ich einen Sperrmechanismus, um dieses Problem zu lösen.

Hier ist mein Arbeitscode:

from lockfile import LockFile
lock = LockFile(lock_file_path)
status = ""
if not lock.is_locked():
    lock.acquire()
    status = lock.path + ' is locked.'
    print status
else:
    status = lock.path + " is already locked."
    print status

return status
Günay Gültekin
quelle
0

Ich habe eine einfache und funktionierende (!) Implementierung von Grizzled-Python gefunden.

Einfache Verwendung os.open (..., O_EXCL) + os.close () funktionierte unter Windows nicht.

Speq
quelle
4
O_EXCL Option ist nicht mit Sperre verbunden
Sergei
0

Sie können Pylocker sehr nützlich finden. Es kann zum Sperren einer Datei oder zum Sperren von Mechanismen im Allgemeinen verwendet werden und kann von mehreren Python-Prozessen gleichzeitig aufgerufen werden.

Wenn Sie eine Datei einfach sperren möchten, funktioniert dies folgendermaßen:

import uuid
from pylocker import Locker

#  create a unique lock pass. This can be any string.
lpass = str(uuid.uuid1())

# create locker instance.
FL = Locker(filePath='myfile.txt', lockPass=lpass, mode='w')

# aquire the lock
with FL as r:
    # get the result
    acquired, code, fd  = r

    # check if aquired.
    if fd is not None:
        print fd
        fd.write("I have succesfuly aquired the lock !")

# no need to release anything or to close the file descriptor, 
# with statement takes care of that. let's print fd and verify that.
print fd
Cobry
quelle