Pergunta

Eu preciso bloquear um arquivo para escrever em Python. Vai ser acessado a partir de vários processos Python ao mesmo tempo. Eu encontrei algumas soluções on-line, mas a maioria não para os meus propósitos como eles são muitas vezes apenas Unix baseado ou baseado no Windows.

Foi útil?

Solução

Tudo bem, então eu acabei indo com o código que eu escrevi aqui, no meu site link está morto, vista em archive.org ( também disponível no GitHub ). Posso usá-lo da seguinte forma:

from filelock import FileLock

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

Outras dicas

Há um módulo de bloqueio de arquivos de plataforma aqui: Portalocker

Embora, como Kevin diz, escrevendo para um arquivo a partir de vários processos ao mesmo tempo é algo que você quer evitar se possível.

Se você pode encaixar o seu problema em um banco de dados, você pode usar SQLite. Ele suporta acesso simultâneo e lida com seu próprio bloqueio.

As outras soluções citar um monte de bases de código externos. Se preferir fazê-lo sozinho, aqui é algum código para uma solução multi-plataforma que utiliza o respectivo arquivo ferramentas de bloqueio em 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        

Agora, AtomicOpen pode ser usado em um bloco with onde um usaria normalmente uma declaração open.

AVISO:. Se executado no Windows e Python trava antes de exit é chamado, eu não sei o que o comportamento de bloqueio seria

AVISO: O bloqueio fornecido aqui é consultivo, e não absoluta. Todos os processos potencialmente concorrentes devem usar a classe "AtomicOpen".

Eu prefiro lockfile - arquivo independente de plataforma bloqueio

O bloqueio é plataforma e dispositivo específico, mas geralmente, você tem algumas opções:

  1. Use flock (), ou equivalente (se seu sistema operacional suporta). Esta é bloqueio de aconselhamento, a menos que você verifique se o bloqueio, a sua ignorado.
  2. Use uma metodologia lock-copy-move-unlock, onde você copiar o arquivo, escrever os novos dados, em seguida, movê-lo (mover, não copiar - movimento é uma operação atômica em Linux - verificar o seu OS), e você verificar a existência do arquivo de bloqueio.
  3. Use um diretório como um "bloqueio". Isso é necessário se você estiver escrevendo para NFS, desde NFS não suporta flock ().
  4. Há também a possibilidade de utilização de memória compartilhada entre os processos, mas eu nunca tentei isso; é muito específico do OS.

Para todos esses métodos, você terá que usar um spin-lock técnica (repetição-pós-falha) para a aquisição e testando o bloqueio. Isso faz deixar uma pequena janela para mis-sincronização, mas a sua geralmente pequenas o suficiente para não ser um grande problema.

Se você está procurando uma solução que é multi-plataforma, então você é melhor fora de registro para outro sistema através de algum outro mecanismo (a próxima melhor coisa é a técnica NFS acima).

Note que sqlite está sujeito às mesmas restrições sobre NFS que os arquivos são normais, então você não pode gravar em um banco de dados SQLite em um compartilhamento de rede e obter sincronização de graça.

Eu tenho estado a olhar para várias soluções para fazer isso e minha escolha foi oslo.concurrency

É poderoso e relativamente bem documentada. É baseado em fixadores.

Outras soluções:

Coordenar o acesso a um único arquivo no nível OS é repleta de todos os tipos de questões que você provavelmente não querem resolver.

Sua melhor aposta é ter um processo separado que as coordenadas de leitura / acesso de gravação para o arquivo.

Bloquear um ficheiro é geralmente uma operação específica da plataforma, assim você pode precisar para permitir a possibilidade de correr em diferentes sistemas operacionais. Por exemplo:

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"

Eu tenho trabalhado em uma situação como esta onde eu executar várias cópias do mesmo programa de dentro os mesmos erros directório / pasta e madeireiras. Minha abordagem era escrever um "arquivo de bloqueio" para o disco antes de abrir o arquivo de log. O programa verifica a presença do "arquivo de bloqueio" antes de prosseguir, e aguarda sua vez, se o "arquivo de bloqueio" existe.

Aqui está o 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')

EDIT --- Depois de pensar sobre alguns dos comentários sobre travas roubadas acima Eu editei o código para adicionar um cheque de staleness do "arquivo de bloqueio." Cronometrando vários milhares de iterações desta função no meu sistema deu e média de 0.002066 ... segundos de apenas antes:

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

para logo depois:

remove('errloglock')

então eu percebi que eu vou começar com 5 vezes esse valor para indicar staleness e monitorar a situação para os problemas.

Além disso, como eu estava trabalhando com o tempo, eu percebi que eu tinha um pouco de código que não era realmente necessário:

lock.close()

que eu tinha imediatamente após a declaração aberta, então eu ter removido-lo nesta edição.

O cenário é assim: O usuário solicita um arquivo para fazer alguma coisa. Então, se o usuário envia o mesmo pedido novamente, ele informa ao usuário que o segundo pedido não é feito até que os primeiros acabamentos solicitação. É por isso, que eu uso lock-mecanismo para lidar com esta questão.

Aqui está o meu código de trabalho:

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

Eu encontrei um simples e trabalhadas (!) de grisalho-python.

O uso simples os.open (..., O_EXCL) + os.close () não funciona no Windows.

Você pode encontrar pylocker muito útil. Ele pode ser utilizado para bloquear um arquivo ou por mecanismos de bloqueio, em geral, e pode ser acedido a partir de vários processos Python de uma só vez.

Se você simplesmente quer bloquear um arquivo aqui está 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top