Preciso bloquear um arquivo para escrever em Python. Ele será acessado de vários processos Python de uma só vez. Encontrei algumas soluções on-line, mas a maioria falha para meus propósitos, pois geralmente são baseadas apenas em Unix ou Windows.
Preciso bloquear um arquivo para escrever em Python. Ele será acessado de vários processos Python de uma só vez. Encontrei algumas soluções on-line, mas a maioria falha para meus propósitos, pois geralmente são baseadas apenas em Unix ou Windows.
Respostas:
Tudo bem, então eu acabei seguindo o código que escrevi aqui, no link do meu site está morto, veja em archive.org ( também disponível no GitHub ). Eu posso usá-lo da seguinte maneira:
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
Há um módulo de bloqueio de arquivos de plataforma cruzada aqui: Portalocker
Embora, como Kevin diz, gravar em um arquivo de vários processos ao mesmo tempo seja algo que você deseja evitar, se possível.
Se você pode colocar o seu problema em um banco de dados, pode usar o SQLite. Ele suporta acesso simultâneo e lida com seu próprio bloqueio.
As outras soluções citam muitas bases de código externas. Se você preferir fazê-lo, aqui está um código para uma solução de plataforma cruzada que usa as respectivas ferramentas de bloqueio de arquivos nos 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 with
bloco em que normalmente se usaria uma open
instrução.
AVISO: Se a execução no Windows e no Python falharem antes da saída ser chamada, não tenho certeza de qual seria o comportamento do bloqueio.
AVISO: O bloqueio fornecido aqui é um aviso, não absoluto. Todos os processos potencialmente concorrentes devem usar a classe "AtomicOpen".
unlock_file
arquivo no linux não deve ligar fcntl
novamente com a LOCK_UN
bandeira?
__exit__
você close
fora da fechadura depois unlock_file
. Acredito que o tempo de execução possa liberar (ou seja, gravar) dados durante close
. Eu acredito que é preciso flush
e fsync
sob o bloqueio para garantir que nenhum dado adicional seja gravado fora do bloqueio durante close
.
flush
e fsync
. Adicionei as duas linhas que você sugeriu antes de ligar unlock
. Testei novamente e a condição de corrida parece estar resolvida.
Estive procurando várias soluções para fazer isso e minha escolha foi oslo.concurrency
É poderoso e relativamente bem documentado. É baseado em fixadores.
Outras soluções:
O bloqueio é específico da plataforma e do dispositivo, mas geralmente você tem algumas opções:
Para todos esses métodos, você precisará usar uma técnica de bloqueio de rotação (repetir após falha) para adquirir e testar o bloqueio. Isso deixa uma pequena janela para sincronização incorreta, mas geralmente é pequena o suficiente para não ser um problema importante.
Se você está procurando uma solução que seja multiplataforma, é melhor fazer logon em outro sistema por outro mecanismo (a próxima melhor coisa é a técnica NFS acima).
Observe que o sqlite está sujeito às mesmas restrições do NFS que os arquivos normais, portanto, você não pode gravar em um banco de dados do sqlite em um compartilhamento de rede e obter sincronização gratuitamente.
os.rename
agora é atômica no Win32 desde o Python 3.3: bugs.python.org/issue8828
A coordenação do acesso a um único arquivo no nível do sistema operacional está repleta de todos os tipos de problemas que você provavelmente não deseja resolver.
Sua melhor aposta é ter um processo separado que coordene o acesso de leitura / gravação a esse arquivo.
flock
. Uma abordagem de "role seus próprios mutexes e um processo de daemon para gerenciá-los" parece ser uma abordagem bastante extrema e complicada a ser tomada para resolver ... um problema que você não nos falou, mas que sugere assustadoramente.
O bloqueio de um arquivo geralmente é uma operação específica da plataforma; portanto, talvez seja necessário permitir a execução 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 essa em que executo várias cópias do mesmo programa de dentro do mesmo diretório / pasta e erros de log. Minha abordagem foi gravar um "arquivo de bloqueio" no disco antes de abrir o arquivo de log. O programa verifica a presença do "arquivo de bloqueio" antes de continuar e aguarda sua vez se o "arquivo de bloqueio" existir.
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')
EDITAR --- Depois de refletir sobre alguns dos comentários sobre bloqueios obsoletos acima, editei o código para adicionar uma verificação de robustez do "arquivo de bloqueio". O tempo de vários milhares de iterações dessa função no meu sistema deu uma média de 0,002066 ... segundos antes:
lock = open('errloglock', 'w')
logo depois:
remove('errloglock')
então imaginei que começaria com 5 vezes esse valor para indicar a rigidez e monitorar a situação quanto a problemas.
Além disso, ao trabalhar com o tempo, percebi que 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, por isso a removi nesta edição.
Para adicionar à resposta de Evan Fossmark , aqui está um exemplo de como usar o 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()
Qualquer código dentro do with lock:
bloco é seguro para threads, o que significa que será concluído antes que outro processo tenha acesso ao arquivo.
O cenário é o seguinte: O usuário solicita um arquivo para fazer alguma coisa. Em seguida, se o usuário enviar a mesma solicitação novamente, ele informará que a segunda solicitação não será feita até que a primeira solicitação seja concluída. É por isso que eu uso o mecanismo de bloqueio para lidar com esse problema.
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 uma implementação simples e trabalhada (!) Do grizzled-python.
O uso simples os.open (..., O_EXCL) + os.close () não funcionou no Windows.
Você pode achar o pylocker muito útil. Ele pode ser usado para bloquear um arquivo ou para mecanismos de bloqueio em geral e pode ser acessado a partir de vários processos Python ao mesmo tempo.
Se você simplesmente deseja bloquear um arquivo, veja como ele 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