Muszę zablokować plik do pisania w Pythonie. Będzie dostępny z wielu procesów Pythona jednocześnie. Znalazłem kilka rozwiązań online, ale większość z nich nie spełnia moich oczekiwań, ponieważ często są one oparte tylko na Uniksie lub Windowsie.
Muszę zablokować plik do pisania w Pythonie. Będzie dostępny z wielu procesów Pythona jednocześnie. Znalazłem kilka rozwiązań online, ale większość z nich nie spełnia moich oczekiwań, ponieważ często są one oparte tylko na Uniksie lub Windowsie.
Odpowiedzi:
W porządku, więc skończyłem z kodem, który napisałem tutaj, na mojej stronie link nie działa, zobacz na archive.org ( również na GitHub ). Mogę go używać w następujący sposób:
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
W tym miejscu znajduje się wieloplatformowy moduł blokowania plików: Portalocker
Chociaż, jak mówi Kevin, zapisywanie do pliku z wielu procesów jednocześnie jest czymś, czego chcesz uniknąć, jeśli to w ogóle możliwe.
Jeśli możesz umieścić problem w bazie danych, możesz użyć SQLite. Obsługuje równoczesny dostęp i obsługuje własne blokowanie.
Inne rozwiązania przytaczają wiele zewnętrznych baz kodu. Jeśli wolisz zrobić to sam, oto kod dla rozwiązania wieloplatformowego, które wykorzystuje odpowiednie narzędzia do blokowania plików w systemach 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
Teraz AtomicOpen
może być używany w with
bloku, w którym normalnie używałbyś open
instrukcji.
OSTRZEŻENIE: Jeśli uruchamianie w systemie Windows i Python ulega awarii przed wywołaniem wyjścia , nie jestem pewien, jakie byłoby zachowanie blokady.
OSTRZEŻENIE: Podana tu blokada jest zalecana, a nie bezwzględna. Wszystkie potencjalnie konkurujące procesy muszą używać klasy „AtomicOpen”.
unlock_file
plik w fcntl
systemie Linux nie powinien dzwonić ponownie z LOCK_UN
flagą?
__exit__
tobie close
poza zamkiem po unlock_file
. Uważam, że środowisko wykonawcze może opróżniać (tj. Zapisywać) dane podczas close
. Uważam, że należy flush
i fsync
pod zamkiem, aby upewnić się, że żadne dodatkowe dane nie zostaną zapisane poza zamkiem podczas close
.
flush
i fsync
. Dodałem dwie linie, które zasugerowałeś, zanim zadzwonisz unlock
. Ponownie przetestowałem i stan wyścigu wydaje się być rozwiązany.
Wolę lockfile - niezależne od platformy blokowanie plików
Szukałem kilku rozwiązań, aby to zrobić, i mój wybór to oslo.concurrency
Jest potężny i stosunkowo dobrze udokumentowany. Opiera się na elementach złącznych.
Inne rozwiązania:
Blokowanie zależy od platformy i urządzenia, ale ogólnie masz kilka opcji:
W przypadku wszystkich tych metod będziesz musiał użyć techniki blokowania spinu (ponowna próba po awarii) w celu uzyskania i przetestowania blokady. To pozostawia małe okno na błędną synchronizację, ale generalnie jest na tyle małe, że nie stanowi poważnego problemu.
Jeśli szukasz rozwiązania wieloplatformowego, lepiej zaloguj się do innego systemu za pomocą innego mechanizmu (następną najlepszą rzeczą jest powyższa technika NFS).
Zauważ, że sqlite podlega tym samym ograniczeniom w systemie plików NFS, co normalne pliki, więc nie możesz pisać do bazy danych sqlite w udziale sieciowym i uzyskiwać synchronizacji za darmo.
os.rename
jest teraz atomowa w Win32 od Pythona 3.3: bugs.python.org/issue8828
Koordynacja dostępu do pojedynczego pliku na poziomie systemu operacyjnego jest pełna różnego rodzaju problemów, których prawdopodobnie nie chcesz rozwiązać.
Najlepiej jest mieć oddzielny proces, który koordynuje dostęp do odczytu / zapisu do tego pliku.
flock
. Podejście „rzuć własne muteksy i proces demona, aby nimi zarządzać” wydaje się być raczej ekstremalnym i skomplikowanym podejściem do rozwiązania ... problem, o którym tak naprawdę nam nie powiedziałeś, ale po prostu sugerowałeś, że istnieje.
Blokowanie pliku jest zwykle operacją specyficzną dla platformy, więc może być konieczne zezwolenie na uruchomienie w różnych systemach operacyjnych. Na przykład:
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"
Pracowałem nad taką sytuacją, w której uruchamiam wiele kopii tego samego programu z tego samego katalogu / folderu i rejestruję błędy. Moje podejście polegało na zapisaniu „pliku blokady” na dysku przed otwarciem pliku dziennika. Program sprawdza obecność „pliku blokady” przed kontynuowaniem i czeka na swoją kolej, jeśli „plik blokady” istnieje.
Oto kod:
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')
EDYTUJ --- Po przemyśleniu niektórych komentarzy dotyczących nieaktualnych blokad powyżej, zredagowałem kod, aby dodać sprawdzenie nieaktualności „pliku blokady”. Czas kilku tysięcy iteracji tej funkcji w moim systemie dał średnio 0,002066 ... sekund tuż przed:
lock = open('errloglock', 'w')
zaraz po:
remove('errloglock')
więc doszedłem do wniosku, że zacznę od 5 razy większej ilości, aby wskazać nieaktualność i monitorować sytuację pod kątem problemów.
Ponadto, pracując z synchronizacją, zdałem sobie sprawę, że mam kawałek kodu, który nie był naprawdę potrzebny:
lock.close()
które miałem natychmiast po otwartym oświadczeniu, więc usunąłem go w tej edycji.
Aby dodać do odpowiedzi Evana Fossmarka , oto przykład użycia blokady 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()
Każdy kod w with lock:
bloku jest bezpieczny dla wątków, co oznacza, że zostanie zakończony, zanim inny proces uzyska dostęp do pliku.
Scenariusz jest tak: użytkownik zażąda plik do zrobienia czegoś. Następnie, jeśli użytkownik ponownie wyśle to samo żądanie, informuje użytkownika, że drugie żądanie nie zostanie wykonane, dopóki pierwsze żądanie nie zakończy się. Dlatego używam mechanizmu blokującego, aby rozwiązać ten problem.
Oto mój działający kod:
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
Znalazłem prostą i działającą (!) Implementację z grizzled-python.
Proste użycie os.open (..., O_EXCL) + os.close () nie działało w systemie Windows.
Pylocker może być bardzo przydatny. Może być używany do blokowania pliku lub ogólnie do blokowania mechanizmów i może być dostępny z wielu procesów Pythona jednocześnie.
Jeśli chcesz po prostu zablokować plik, oto jak to działa:
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