Devo bloccare un file per scrivere in Python. Vi si accederà da più processi Python contemporaneamente. Ho trovato alcune soluzioni online, ma la maggior parte fallisce per i miei scopi in quanto sono spesso solo basati su Unix o basati su Windows.
Devo bloccare un file per scrivere in Python. Vi si accederà da più processi Python contemporaneamente. Ho trovato alcune soluzioni online, ma la maggior parte fallisce per i miei scopi in quanto sono spesso solo basati su Unix o basati su Windows.
Risposte:
Bene, quindi ho finito con il codice che ho scritto qui, sul mio sito web il link è morto, visualizza su archive.org ( disponibile anche su GitHub ). Posso usarlo nel modo seguente:
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
Esiste un modulo di blocco dei file multipiattaforma qui: Portalocker
Anche se come dice Kevin, scrivere su un file da più processi contemporaneamente è qualcosa che vuoi evitare se possibile.
Se puoi calzare il tuo problema in un database, puoi usare SQLite. Supporta l'accesso simultaneo e gestisce il proprio blocco.
Le altre soluzioni citano molte basi di codice esterne. Se preferisci farlo da solo, ecco un po 'di codice per una soluzione multipiattaforma che utilizza i rispettivi strumenti di blocco dei file su sistemi Linux / DOS.
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
Ora, AtomicOpen
può essere utilizzato in un with
blocco in cui si userebbe normalmente open
un'istruzione.
ATTENZIONE: se l'esecuzione su Windows e Python si arresta in modo anomalo prima che venga chiamato exit , non sono sicuro di quale sarebbe il comportamento del blocco.
ATTENZIONE: il blocco fornito qui è consultivo, non assoluto. Tutti i processi potenzialmente concorrenti devono utilizzare la classe "AtomicOpen".
unlock_file
file su linux non dovrebbe richiamare di fcntl
nuovo con il LOCK_UN
flag?
__exit__
voi close
al di fuori della serratura dopo unlock_file
. Credo che il runtime potrebbe scaricare (cioè scrivere) i dati durante close
. Credo che si debba flush
e fsync
sotto il blocco per assicurarsi che nessun dato aggiuntivo venga scritto all'esterno del blocco durante close
.
flush
e fsync
. Ho aggiunto le due linee che hai suggerito prima di chiamare unlock
. Ho testato nuovamente e le condizioni di gara sembrano essere state risolte.
Preferisco lockfile - Blocco dei file indipendente dalla piattaforma
Ho cercato diverse soluzioni per farlo e la mia scelta è stata oslo.concurrency
È potente e relativamente ben documentato. Si basa su elementi di fissaggio.
Altre soluzioni:
Il blocco è specifico per piattaforma e dispositivo, ma in genere sono disponibili alcune opzioni:
Per tutti questi metodi, dovrai utilizzare una tecnica spin-lock (retry-after-failure) per acquisire e testare il blocco. Questo lascia una piccola finestra per una errata sincronizzazione, ma è generalmente abbastanza piccolo da non essere un grosso problema.
Se stai cercando una soluzione che sia multipiattaforma, allora è meglio accedere a un altro sistema tramite qualche altro meccanismo (la prossima cosa migliore è la tecnica NFS sopra).
Si noti che sqlite è soggetto agli stessi vincoli su NFS dei file normali, quindi non è possibile scrivere su un database sqlite su una condivisione di rete e ottenere la sincronizzazione gratuitamente.
os.rename
ora è atomico in Win32 da Python 3.3: bugs.python.org/issue8828
Il coordinamento dell'accesso a un singolo file a livello di sistema operativo è irto di tutti i tipi di problemi che probabilmente non si desidera risolvere.
La tua scommessa migliore è avere un processo separato che coordina l'accesso in lettura / scrittura a quel file.
flock
. Un approccio di "tira i tuoi mutex e un processo daemon per gestirli" sembra un approccio piuttosto estremo e complicato da adottare per risolvere ... un problema di cui non ci hai effettivamente parlato, ma che è appena stato suggerito.
Il blocco di un file è in genere un'operazione specifica della piattaforma, pertanto potrebbe essere necessario consentire la possibilità di essere eseguito su diversi sistemi operativi. Per esempio:
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"
Ho lavorato su una situazione come questa in cui eseguo più copie dello stesso programma all'interno della stessa directory / cartella ed errori di registrazione. Il mio approccio era quello di scrivere un "file di blocco" sul disco prima di aprire il file di registro. Il programma verifica la presenza del "file di blocco" prima di procedere e attende il suo turno se esiste il "file di blocco".
Ecco il codice:
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')
MODIFICA --- Dopo aver riflettuto su alcuni dei commenti sui blocchi obsoleti sopra ho modificato il codice per aggiungere un controllo per la staleness del "file di blocco". Il tempismo di diverse migliaia di iterazioni di questa funzione sul mio sistema ha dato e una media di 0,002066 ... secondi da poco prima:
lock = open('errloglock', 'w')
subito dopo:
remove('errloglock')
così ho pensato che inizierò con 5 volte tale importo per indicare la stanchezza e monitorare la situazione per problemi.
Inoltre, mentre stavo lavorando con i tempi, mi sono reso conto che avevo un po 'di codice che non era davvero necessario:
lock.close()
che ho subito seguito alla dichiarazione aperta, quindi l'ho rimosso in questa modifica.
Per aggiungere alla risposta di Evan Fossmark , ecco un esempio di come utilizzare 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()
Qualsiasi codice all'interno del with lock:
blocco è thread-safe, il che significa che sarà completato prima che un altro processo abbia accesso al file.
Lo scenario è simile: l'utente richiede un file per fare qualcosa. Quindi, se l'utente invia nuovamente la stessa richiesta, informa l'utente che la seconda richiesta non viene eseguita fino al termine della prima richiesta. Ecco perché, uso il meccanismo di blocco per gestire questo problema.
Ecco il mio codice di lavoro:
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
Ho trovato un'implementazione semplice e funzionante (!) Da Grizzled-Python.
Uso semplice os.open (..., O_EXCL) + os.close () non ha funzionato su Windows.
Potresti trovare molto utile pylocker . Può essere usato per bloccare un file o per meccanismi di blocco in generale e vi si può accedere da più processi Python contemporaneamente.
Se vuoi semplicemente bloccare un file, ecco come funziona:
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