Блокировка файла в Python
-
20-08-2019 - |
Вопрос
Мне нужно заблокировать файл для записи на Python.Доступ к нему будет осуществляться из нескольких процессов Python одновременно.Я нашел несколько решений в Интернете, но большинство из них не подходят для моих целей, поскольку часто они основаны только на Unix или Windows.
Решение
Хорошо, итак, в итоге я перешел к написанному мной коду здесь, на моем сайте ссылка мертва, просмотр на archive.org (также доступно на GitHub).Я могу использовать его следующим образом:
from filelock import FileLock
with FileLock("myfile.txt"):
# work with the file as it is now locked
print("Lock acquired.")
Другие советы
Здесь есть кроссплатформенный модуль блокировки файлов: Блокировка портала
Хотя, как говорит Кевин, одновременной записи в файл из нескольких процессов лучше избегать, если это вообще возможно.
Если вы можете перенести свою проблему в базу данных, вы могли бы использовать SQLite.Он поддерживает параллельный доступ и обрабатывает свою собственную блокировку.
Другие решения ссылаются на множество внешних кодовых баз.Если вы предпочитаете сделать это самостоятельно, вот некоторый код для кроссплатформенного решения, которое использует соответствующие инструменты блокировки файлов в системах 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
Сейчас, AtomicOpen
может быть использован в with
блок, в котором обычно используется open
заявление.
ПРЕДУПРЕЖДЕНИЕ: Если запуск в Windows и Python завершается сбоем раньше выход вызывается, я не уверен, каким было бы поведение блокировки.
ПРЕДУПРЕЖДЕНИЕ: Предусмотренная здесь блокировка носит рекомендательный, а не абсолютный характер.Все потенциально конкурирующие процессы должны использовать класс "AtomicOpen".
Я предпочитаю файл блокировки — Блокировка файлов, не зависящая от платформы
Блокировка зависит от платформы и устройства, но, как правило, у вас есть несколько вариантов:
- Используйте flock() или эквивалент (если ваша ос это поддерживает).Это рекомендательная блокировка, если вы не проверите наличие блокировки, она игнорируется.
- Используйте методологию блокировки-копирования-перемещения-разблокировки, при которой вы копируете файл, записываете новые данные, затем перемещаете его (перемещение, а не копирование - перемещение - это атомарная операция в Linux - проверьте свою ОС), и вы проверяете существование файла блокировки.
- Используйте каталог в качестве "блокировки".Это необходимо, если вы пишете в NFS, поскольку NFS не поддерживает flock().
- Существует также возможность использования общей памяти между процессами, но я никогда не пробовал этого;это очень специфично для операционной системы.
Для всех этих методов вам придется использовать метод спин-блокировки (повторная попытка после сбоя) для получения и тестирования блокировки.Это оставляет небольшое окно для неправильной синхронизации, но, как правило, оно достаточно мало, чтобы не быть серьезной проблемой.
Если вы ищете кроссплатформенное решение, то вам лучше войти в другую систему с помощью какого-либо другого механизма (следующая лучшая вещь - описанный выше метод NFS).
Обратите внимание, что на sqlite распространяются те же ограничения в отношении NFS, что и на обычные файлы, поэтому вы не можете выполнить запись в базу данных sqlite на сетевом ресурсе и получить синхронизацию бесплатно.
Я рассматривал несколько решений для этого, и мой выбор был осло.параллелизм
Это мощная и относительно хорошо документированная программа.Он основан на крепежных элементах.
Другие решения:
- Блокировка портала:требуется pywin32, который является exe-установкой, поэтому невозможен через pip
- крепежные детали:плохо документированный
- файл блокировки:устаревший
- flufl.замок:NFS-безопасная блокировка файлов для систем POSIX.
- простой замок :Последнее обновление 2013-07
- zc.файл блокировки :Последнее обновление 2016-06 (по состоянию на 2017-03)
- файл блокировки :Последнее обновление в 2007-10
Координация доступа к одному файлу на уровне операционной системы сопряжена со всевозможными проблемами, которые вы, вероятно, не захотите решать.
Лучше всего создать отдельный процесс, который координирует доступ на чтение / запись к этому файлу.
Блокировка файла обычно является операцией, зависящей от платформы, поэтому вам может потребоваться предусмотреть возможность запуска в разных операционных системах.Например:
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"
Я работал над подобной ситуацией, когда я запускаю несколько копий одной и той же программы из одного и того же каталога / папки и регистрирую ошибки.Мой подход состоял в том, чтобы записать "файл блокировки" на диск перед открытием файла журнала.Программа проверяет наличие "файла блокировки", прежде чем продолжить, и ожидает своей очереди, если "файл блокировки" существует.
Вот этот код:
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')
Редактировать--- Обдумав некоторые комментарии об устаревших блокировках выше, я отредактировал код, чтобы добавить проверку на устаревание "файла блокировки". Синхронизация нескольких тысяч итераций этой функции в моей системе дала среднее значение 0.002066...за несколько секунд до того, как:
lock = open('errloglock', 'w')
сразу после:
remove('errloglock')
поэтому я решил, что начну с 5-кратной суммы, чтобы указать на несвежесть и отслеживать ситуацию на предмет проблем.
Кроме того, работая с хронометражем, я понял, что у меня есть немного кода, который на самом деле не был необходим:
lock.close()
который у меня появился сразу после открытого заявления, поэтому я удалил его в этой правке.
В сценарий это похоже на то, что:Пользователь запрашивает файл, чтобы что-то сделать.Затем, если пользователь снова отправляет тот же запрос, он информирует пользователя о том, что второй запрос не будет выполнен до завершения первого запроса.Вот почему я использую механизм блокировки для решения этой проблемы.
Вот мой рабочий код:
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
Я нашел простой и работающий (!) реализация от седого питона.
Простое использование os.open(..., O_EXCL) + os.close() не сработало в Windows.
Вы можете найти блокиратор очень полезно.Он может использоваться для блокировки файла или для механизмов блокировки в целом, и к нему можно получить доступ из нескольких процессов Python одновременно.
Если вы просто хотите заблокировать файл, вот как это работает:
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