загрузка JSON из заблокированного файла - PullRequest
0 голосов
/ 23 мая 2018

У меня есть несколько экземпляров одного и того же сценария Python, работающего параллельно, читающего и записывающего в один и тот же файл json: сначала экземпляр считывает информацию из файла json, затем обрабатывает ее, затем блокирует, затем снова читает, чтобы получитьна сегодняшний день содержимое файла (возможно, было изменено другими экземплярами) затем записывает в него и снимает блокировку.То есть, вот как это будет работать, если это ... сработало

Урезанная версия блокирующей и записывающей части в моем скрипте выглядит так:

import json
import fcntl

data = json.load(open('test.json'))

# do things with data

with open('test.json', 'w+') as file:
    fcntl.flock(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
    data = json.load(open('test.json'))
    fcntl.flock(file, fcntl.LOCK_UN)

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

Как мне правильно настроить этот файл?

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

Но функция открытия вроде как очищает файл

Да, открытие файла в режиме записи w всегда очищает файл;из open() документации по функциям :

'w'
открыть для записи, сначала обрезать файл

[...] По умолчаниюрежим 'r' (открыт для чтения текста, синоним 'rt').Для двоичного доступа на чтение и запись, режим 'w+b' открывает и усекает файл до 0 байтов.'r+b' открывает файл без усечения.

Вы хотите заблокировать файл до его усечения .Вы также можете открыть файл в режиме 'r+' (чтение и запись), после чего вам необходимо вручную обрезать его после блокировки.

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

Лично я бы создал менеджер контекста, который обрабатывает блокировку (либо вэксклюзивный режим для записи или в режиме совместного использования для чтения), и обрезать файл только после получения блокировки.Вам также нужно будет учесть, что файл еще не существует, и если вы не хотите ждать блокировок вечно, вам нужно обрабатывать таймауты (то есть вам нужно использовать LOCK_NB в цикле и проверьте возвращаемое значение, чтобы увидеть, была ли получена блокировка, до истечения определенного времени).

В следующем диспетчере контекста я использовал системный вызов низкого уровня os.open(), чтобы гарантироватьфайл создается при попытке заблокировать его для монопольного доступа без его усечения, если он уже существует :

import errno
import fcntl
import os
import time

class Timeout(Exception):
    """Could not obtain a lock within the time given"""

class LockedFile:
    """Lock and open a file.

    If the file is opened for writing, an exclusive lock is used,
    otherwise it is a shared lock

    """
    def __init__(self, path, mode, timeout=None, **fileopts):
        self.path = path
        self.mode = mode
        self.fileopts = fileopts
        self.timeout = timeout
        # lock in exclusive mode when writing or appending (including r+)
        self._exclusive = set('wa+').intersection(mode)
        self._lockfh = None
        self._file = None

    def _acquire(self):
        if self._exclusive:
            # open the file in write & create mode, but *without the 
            # truncate flag* to make sure it is created only if it 
            # doesn't exist yet
            lockfhmode, lockmode = os.O_WRONLY | os.O_CREAT, fcntl.LOCK_EX
        else:
            lockfhmode, lockmode = os.O_RDONLY, fcntl.LOCK_SH
        self._lockfh = os.open(self.path, lockfhmode)
        start = time.time()
        while True:
            try:
                fcntl.lockf(self._lockfh, lockmode | fcntl.LOCK_NB)
                return
            except OSError as e:
                if e.errno not in {errno.EACCES, errno.EAGAIN}:
                    raise
            if self.timeout is not None and time.time() - start > self.timeout:
                raise Timeout()
            time.sleep(0.1)

    def _release(self):
        fcntl.lockf(self._lockfh, fcntl.LOCK_UN)
        os.close(self._lockfh)

    def __enter__(self):
        if self._file is not None:
            raise LockException('Lock already taken')
        self._acquire()
        self._file = open(self.path, self.mode, **self.fileopts)
        return self._file

    def __exit__(self, *exc):
        if self._file is None:
            raise LockException('Not locked')
        self._file.close()
        self._file = None
        self._release()

Процессы, которые пытаются прочитать файл, затем используют:

with LockedFile('test.json', 'r') as file:
    data = json.load(file)

и процесс, который хочет записать, использует:

with LockedFile('test.json', 'w') as file:
    json.dump(data, file)

Если вы хотите разрешить тайм-аут, добавьте блок try/except вокруг блока with и поймайте исключение Timeout;вам нужно решить, что должно произойти:

try:
    with LockedFile('test.json', 'w', timeout=10) as file:
        json.dump(data, file)
except Timeout:
    # could not acquire an exclusive lock to write the file. What now?
0 голосов
/ 23 мая 2018

Вы использовали "w +" для открытия файла.

w + Открывает файл для записи и чтения.Перезаписывает существующий файл, если файл существует.Если файл не существует, создает новый файл для чтения и записи.

Поэтому вместо w+ используйте a.

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

Источник: www.tutorialspoint.com , Документы Python

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...