Bloqueando un archivo en Python

Necesito bloquear un archivo para escribir en Python. Se accederá desde varios procesos de Python a la vez. He encontrado algunas soluciones en línea, pero la mayoría falla para mis propósitos, ya que a menudo solo están basadas en Unix o en Windows.

Bien, entonces terminé yendo con el código que escribí aquí, en el enlace de mi sitio web está muerto, ver en archive.org ( también disponible en GitHub ). Puedo usarlo de la siguiente manera:

from filelock import FileLock with FileLock("myfile.txt"): # work with the file as it is now locked print("Lock acquired.") 

Hay un módulo de locking de archivos multiplataforma aquí: Portalocker

Aunque como dice Kevin, escribir en un archivo desde varios procesos a la vez es algo que debes evitar si es posible.

Si puede convertir su problema en una base de datos, puede usar SQLite. Admite el acceso simultáneo y maneja su propio locking.

Prefiero lockfile – Bloqueo de archivos independiente de la plataforma

El locking es específico de la plataforma y el dispositivo, pero en general, tiene algunas opciones:

  1. Utilice flock () o equivalente (si su sistema operativo lo admite). Este es un locking de advertencia, a menos que compruebe el locking, se ignora.
  2. Use una metodología de locking-copia-movimiento-deslocking, en la que copia el archivo, escribe los nuevos datos, luego muévalo (mover, no copiar – mover es una operación atómica en Linux – verifique su sistema operativo), y usted verifica el existencia del archivo de locking.
  3. Utilice un directorio como un “locking”. Esto es necesario si está escribiendo en NFS, ya que NFS no admite flock ().
  4. También existe la posibilidad de usar memoria compartida entre los procesos, pero nunca lo he intentado; Es muy específico del sistema operativo.

Para todos estos métodos, tendrá que usar una técnica de locking de giro (reintentar después de la falla) para adquirir y probar el locking. Esto deja una pequeña ventana para la falta de sincronización, pero generalmente es lo suficientemente pequeño como para no ser un problema importante.

Si está buscando una solución que sea multiplataforma, es mejor que inicie sesión en otro sistema a través de algún otro mecanismo (la siguiente mejor opción es la técnica de NFS que se muestra más arriba).

Tenga en cuenta que sqlite está sujeto a las mismas restricciones sobre NFS que los archivos normales, por lo que no puede escribir en una base de datos sqlite en un recurso compartido de red y obtener la sincronización de forma gratuita.

Las otras soluciones citan muchas bases de código externas. Si prefiere hacerlo usted mismo, aquí hay un código para una solución multiplataforma que utiliza las respectivas herramientas de locking de archivos en los sistemas 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 

Ahora, AtomicOpen se puede usar en un bloque with donde normalmente se usaría una instrucción open .

ADVERTENCIA: si la ejecución en Windows y Python se bloquea antes de llamar a exit , no estoy seguro de cuál sería el comportamiento de locking.

ADVERTENCIA: El locking provisto aquí es de advertencia, no absoluto. Todos los procesos potencialmente competidores deben usar la clase “AtomicOpen”.

Coordinar el acceso a un solo archivo en el nivel del sistema operativo está plagado de todo tipo de problemas que probablemente no quiera resolver.

Su mejor apuesta es tener un proceso separado que coordine el acceso de lectura / escritura a ese archivo.

He estado buscando varias soluciones para hacer eso y mi elección ha sido oslo.concurrency

Es potente y relativamente bien documentado. Se basa en sujetadores.

Otras soluciones:

  • Portalocker : requiere pywin32, que es una instalación exe, por lo que no es posible a través de pip
  • cierres : mal documentados
  • archivo de locking : en desuso
  • flufl.lock : Bloqueo de archivos seguro para NFS para sistemas POSIX.
  • simpleflock : última actualización 2013-07
  • zc.lockfile : última actualización 2016-06 (a partir de 2017-03)
  • lock_file : última actualización en 2007-10

El locking de un archivo suele ser una operación específica de la plataforma, por lo que es posible que tenga que permitir la posibilidad de ejecutarse en diferentes sistemas operativos. Por ejemplo:

 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" 

El escenario es así: el usuario solicita un archivo para hacer algo. Luego, si el usuario vuelve a enviar la misma solicitud, le informa que la segunda solicitud no se realiza hasta que la primera solicitud finalice. Por eso, uso el mecanismo de locking para manejar este problema.

Aquí está mi código de trabajo:

 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 

He estado trabajando en una situación como esta en la que ejecuto varias copias del mismo progtwig desde el mismo directorio / carpeta y errores de registro. Mi enfoque fue escribir un “archivo de locking” en el disco antes de abrir el archivo de registro. El progtwig verifica la presencia del “archivo de locking” antes de continuar, y espera su turno si el “archivo de locking” existe.

Aquí está el código:

 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') 

EDITAR — Después de reflexionar sobre algunos de los comentarios sobre los lockings pasados ​​de arriba, edité el código para agregar una verificación de la caducidad del “archivo de locking”. La sincronización de varios miles de iteraciones de esta función en mi sistema dio un promedio de 0,002066 … segundos antes de:

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

justo después

 remove('errloglock') 

así que pensé que comenzaría con 5 veces esa cantidad para indicar la falta de control y monitorear la situación en busca de problemas.

Además, mientras trabajaba con la sincronización, me di cuenta de que tenía un poco de código que no era realmente necesario:

 lock.close() 

que tuve inmediatamente después de la statement abierta, por lo que la he eliminado en esta edición.

Encontré una implementación simple y trabajada (!) De grizzled-python.

El uso simple os.open (…, O_EXCL) + os.close () no funcionó en Windows.

Usted puede encontrar Pylocker muy útil. Puede usarse para bloquear un archivo o para bloquear mecanismos en general y se puede acceder a él desde múltiples procesos de Python a la vez.

Si simplemente quiere bloquear un archivo, así es como funciona:

 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