Блокировка файла в Python - PullRequest
121 голосов
/ 29 января 2009

Мне нужно заблокировать файл для записи на Python. Он будет доступен сразу из нескольких процессов Python. Я нашел некоторые решения в Интернете, но большинство из них не подходят для моих целей, поскольку они часто основаны только на Unix или Windows.

Ответы [ 12 ]

93 голосов
/ 31 января 2009

Хорошо, поэтому я закончил с кодом, который я написал здесь, на моем сайте ссылка не работает, просмотр на archive.org ( также доступно на GitHub ). Я могу использовать его следующим образом:

from filelock import FileLock

with FileLock("myfile.txt"):
    # work with the file as it is now locked
    print("Lock acquired.")
38 голосов
/ 29 января 2009

Здесь есть кроссплатформенный модуль блокировки файлов: Portalocker

Хотя, как говорит Кевин, запись в файл из нескольких процессов одновременно - это то, чего вы хотите избежать, если это вообще возможно.

Если вы можете поместить вашу проблему в базу данных, вы можете использовать SQLite. Он поддерживает одновременный доступ и обрабатывает собственную блокировку.

15 голосов
/ 27 июля 2010

Я предпочитаю lockfile - независимая от платформы блокировка файлов

14 голосов
/ 25 сентября 2017

Другие решения ссылаются на множество внешних кодовых баз. Если вы предпочитаете делать это самостоятельно, вот код для кроссплатформенного решения, использующего соответствующие инструменты блокировки файлов в системах 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 происходит сбой до вызова exit , я не уверен, каково было бы поведение блокировки.

ПРЕДУПРЕЖДЕНИЕ: Приведенная здесь блокировка носит рекомендательный, а не абсолютный характер. Все потенциально конкурирующие процессы должны использовать класс «AtomicOpen».

12 голосов
/ 29 января 2009

Блокировка зависит от платформы и устройства, но, как правило, у вас есть несколько вариантов:

  1. Используйте flock () или его эквивалент (если ваша ОС поддерживает это). Это рекомендательная блокировка, если вы не проверите ее, она игнорируется.
  2. Используйте методологию lock-copy-move-unlock, где вы копируете файл, записываете новые данные, затем перемещаете его (перемещаете, а не копируете - перемещение - это атомарная операция в Linux - проверьте вашу ОС), и вы проверьте наличие файла блокировки.
  3. Использовать каталог как «замок». Это необходимо, если вы пишете в NFS, поскольку NFS не поддерживает flock ().
  4. Существует также возможность использования общей памяти между процессами, но я никогда не пробовал этого; это очень зависит от ОС.

Для всех этих методов вам придется использовать технику спин-блокировки (повторная попытка сбоя) для получения и тестирования блокировки. Это оставляет маленькое окно для неправильной синхронизации, но обычно оно достаточно маленькое, чтобы не быть основной проблемой.

Если вы ищете решение, которое является кроссплатформенным, то вам лучше подключиться к другой системе с помощью какого-либо другого механизма (следующая лучшая вещь - это метод NFS, описанный выше).

Обратите внимание, что на sqlite распространяются те же ограничения на NFS, что и на обычные файлы, поэтому вы не можете писать в базу данных sqlite на сетевом ресурсе и получать синхронизацию бесплатно.

8 голосов
/ 07 декабря 2015

Я искал несколько решений, чтобы сделать это, и мой выбор был oslo.concurrency

Это мощный и относительно хорошо документированный. Он основан на крепежных деталях.

Другие решения:

  • Portalocker : требуется pywin32, которая является установкой exe, поэтому невозможна через pip
  • крепеж : плохо документирован
  • файл блокировки : устарело
  • flufl.lock : NFS-безопасная блокировка файлов для систем POSIX.
  • simpleflock : последнее обновление 2013-07
  • zc.lockfile : последнее обновление 2016-06 (по состоянию на 2017-03-03)
  • lock_file : последнее обновление 2007-10
7 голосов
/ 29 января 2009

Координация доступа к одному файлу на уровне ОС чревата всевозможными проблемами, которые вы, вероятно, не хотите решать.

Ваша лучшая ставка - это отдельный процесс, который координирует доступ для чтения / записи к этому файлу.

5 голосов
/ 29 января 2009

Блокировка файла, как правило, зависит от платформы, поэтому вам может потребоваться возможность запуска в разных операционных системах. Например:

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"
1 голос
/ 11 августа 2017

Сценарий выглядит так: Пользователь запрашивает файл, чтобы что-то сделать. Затем, если пользователь снова отправляет тот же запрос, он информирует пользователя о том, что второй запрос не выполнен, пока не завершится первый. Вот почему я использую механизм блокировки для решения этой проблемы.

Вот мой рабочий код:

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
1 голос
/ 07 августа 2014

Я работал в такой ситуации, когда я запускаю несколько копий одной и той же программы из одной и той же директории / папки и регистрирую ошибки. Мой подход состоял в том, чтобы записать «файл блокировки» на диск перед открытием файла журнала. Программа проверяет наличие "файла блокировки", прежде чем продолжить, и ждет его очереди, если "файл блокировки" существует.

Вот код:

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 --- Подумав над комментариями о устаревших блокировках, приведенных выше, я отредактировал код, добавив проверку на устаревание «файла блокировки». Время выполнения нескольких тысяч итераций этой функции в моей системе составило, в среднем, 0,002066 ... секунд по сравнению с предыдущим:

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

только после:

remove('errloglock')

поэтому я подумал, что начну с пятикратного увеличения этой суммы, чтобы указать на устаревание и отслеживать ситуацию на предмет проблем.

Кроме того, когда я работал с синхронизацией, я понял, что у меня есть немного кода, который на самом деле не нужен:

lock.close()

, который у меня был сразу после оператора open, поэтому я удалил его в этом редакторе.

...