Pregunta

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

¿Fue útil?

Solución

Muy bien, así que terminé yendo con el código que escribí aquí, en mi sitio web el enlace está inactivo, 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.")

Otros consejos

Aquí hay un módulo de bloqueo de archivos multiplataforma: Portalocker

Aunque, como dice Kevin, escribir en un archivo desde múltiples procesos a la vez es algo que desea evitar si es posible.

Si puede calzar su problema en una base de datos, puede usar SQLite. Admite acceso concurrente y maneja su propio bloqueo.

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 bloqueo de archivos en 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 declaración open.

ADVERTENCIA: Si se ejecuta en Windows y Python se bloquea antes de que se llame a salir , no estoy seguro de cuál sería el comportamiento del bloqueo.

ADVERTENCIA: El bloqueo proporcionado aquí es informativo, no absoluto. Todos los procesos potencialmente competitivos deben usar & Quot; AtomicOpen & Quot; clase.

Prefiero lockfile & # 8212; Bloqueo de archivos independiente de la plataforma

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

  1. Use flock (), o equivalente (si su sistema operativo lo admite). Este es un bloqueo de aviso, a menos que compruebe el bloqueo, se ignora.
  2. Utilice una metodología de bloqueo-copia-movimiento-desbloqueo, donde copie el archivo, escriba los nuevos datos, luego muévalo (mover, no copiar - mover es una operación atómica en Linux - verifique su sistema operativo), y usted compruebe la existencia del archivo de bloqueo.
  3. Use un directorio como " lock " ;. 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 bloqueo de giro (reintentar después del fallo) para adquirir y probar el bloqueo. Esto deja una pequeña ventana para la sincronización incorrecta, 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 NFS anterior).

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.

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

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

Otras soluciones:

La coordinación del acceso a un solo archivo a nivel del sistema operativo está llena de todo tipo de problemas que probablemente no desee resolver.

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

El bloqueo de un archivo suele ser una operación específica de la plataforma, por lo que es posible que deba 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"

He estado trabajando en una situación como esta en la que ejecuto varias copias del mismo programa desde el mismo directorio / carpeta y errores de registro. Mi enfoque fue escribir un & "; Bloquear el archivo &"; al disco antes de abrir el archivo de registro. El programa verifica la presencia de & Quot; archivo de bloqueo & Quot; antes de continuar, y espera su turno si el " archivo de bloqueo " 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 pensar en algunos de los comentarios sobre los bloqueos obsoletos anteriores, edité el código para agregar una verificación de la obsolescencia del & Quot; archivo de bloqueo. & Quot; El tiempo de varios miles de iteraciones de esta función en mi sistema dio un promedio de 0.002066 ... segundos desde justo antes:

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

a justo después:

remove('errloglock')

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

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

lock.close()

que tenía inmediatamente después de la declaración abierta, así que la eliminé en esta edición.

El escenario es así: El usuario solicita un archivo para hacer algo. Luego, si el usuario envía la misma solicitud nuevamente, le informa que la segunda solicitud no se realiza hasta que finaliza la primera solicitud. Por eso, uso el mecanismo de bloqueo 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

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

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

Puede encontrar pylocker muy útil. Se puede usar para bloquear un archivo o para bloquear mecanismos en general y se puede acceder desde múltiples procesos de Python a la vez.

Si simplemente desea 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top