【问题标题】:loading json from a locked file从锁定的文件加载 json
【发布时间】:2018-05-23 09:45:54
【问题描述】:

我有多个并行运行的相同 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 函数似乎有点清除文件,因为运行此 sn-p 后它将为空,并且 json 抱怨文件格式无效。

如何正确设置?

【问题讨论】:

  • 你为什么使用LOCK_NB?在没有测试的情况下无法获得锁并不是一个好主意,真的。
  • 我只是跟着一个教程。以前从未使用过此模块。
  • 这是一个非常底层的系统模块;也许使用更高级别的包装器,例如 filelock library?

标签: python json file locking


【解决方案1】:

但是打开功能似乎有点清除文件

是的,以w 写入模式打开文件总是会清除文件;来自open() function documentation

'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 LockException(Exception):
    """General (file) locking-related exception"""

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()
        try:
            self._file = open(self.path, self.mode, **self.fileopts)
        except IOException:
            self._release()
            raise
        return self._file

    def __exit__(self, *exc):
        if self._file is None:
            raise LockException('Not locked')
        try:
            self._file.close()
        finally:
            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)

如果要允许超时,请在with 块周围添加try/except 块并捕获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?

【讨论】:

    【解决方案2】:

    您使用“w+”打开文件。

    w+ 打开一个文件进行写入和读取。如果文件存在,则覆盖现有文件。如果文件不存在,则新建一个文件进行读写。

    所以不要使用w+,而是使用a

    在我看来,您真的可以通过使用锁以更优雅的方式使用线程库或多处理来执行此操作,而不是运行同一 python 脚本的多个实例。

    来源:www.tutorialspoint.comPython Docs

    【讨论】:

    • 在某些平台上,当以追加模式打开时,操作系统会阻止对结束点之前的文件进行任何形式的访问。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    相关资源
    最近更新 更多