如何在Linux中原子地创建一个锁定的文件?

14
场景:我有许多需要从网络上获取文件的进程。如果文件已经下载,我希望将它缓存在磁盘上。如果另一个进程正在下载该文件,则阻塞直到下载完成。
我一直在尝试找到最简单的方法来实现这一点。显而易见的方法是:
create file w/ an exclusive lock active on it only if it doesn't exist (O_CREAT | O_EXCL)
if file exists already:
   open file and acquire exclusive lock
else:
   download to newly created file
release lock

这个系统似乎没有竞争条件,实现了上述目标。
不幸的是,我找不到关于如何使用open()等在Linux中创建锁定文件的文档。如果我将创建步骤分为:
open w/ O_CREAT | O_EXCL
flock

现在“创建”和“锁定”之间存在竞争条件(非创建进程在创建者之前获取锁)。
我意识到我可以针对每个文件使用外部锁文件(例如文件名+'.lock'),在尝试创建文件名之前获取它,但这感觉...不优雅(我现在需要担心实际上带有.lock后缀的文件!)
是否有一种方法可以原子地创建和锁定它(如Windows所提供的),或者外部锁文件方法基本上是标准/必需的?
3个回答

11
比如文件可能存在,也可能不存在,那么在尝试锁定该文件之前必须先测试其是否存在。但如果将文件作为互斥锁,您就无法做到这一点,否则会出现“如果文件已经存在”(false)和“下载到新创建的文件”之间的空间限制不明确的情况。另一个进程可能会在您的下载开始之前创建文件并开始下载,而您可能会覆盖它。 基本上在此处不要使用fcntl锁定,而是使用文件本身的存在状况。 带有O_CREAT和O_EXCL的open()将在文件已经存在时失败,告诉您其他人已经获取到首先的机会。

1
如果我可以原子性地创建一个锁定文件(仅当文件不存在时),我不确定竞争如何存在。(条件也是原子性的)。O_CREAT | O_EXCL是如果文件不存在则原子性地创建;我只想用锁来做到这一点。另一个进程无法开始下载,因为它会检测到文件已经存在。最后,我不能仅使用open,因为我必须阻塞直到下载完成;在我的解决方案中,我依靠(独占读/写)锁。 - UsAaR33
2
你是对的,原子地创建和锁定文件可以解决你的问题。但是很抱歉,这不是fcntl锁的工作方式。如果你想使用fcntl锁,被锁定的文件必须在程序执行任何同步操作之前存在。找到一种方法将创建文件的过程移到if()之外,或者设计另一种不使用下载文件作为互斥体的同步协议。 - Andy Ross
1
明白了,那么答案就是Linux没有像Windows那样在打开文件时提供原子锁。我会继续使用lockfile方法。 - UsAaR33

1

为什么不使用锁文件工具?

示例

假设您想确保对文件“important”的访问是串行的,即不允许多个程序或Shell脚本同时访问它。为简单起见,假设它是一个Shell脚本。在这种情况下,您可以像这样解决它:

...
lockfile important.lock
...
access_"important"_to_your_hearts_content
...
rm -f important.lock
...

0

我目前正在努力解决一个类似的问题,这也是我来到你的问题的原因。在我看来,关键是:

int fd = open(path, O_CREAT|O_RDWR|O_EXCL, mode);
if (fd == -1)
    {
    /* File already exists. */
    the_file_already_exists(fd);
    }
else
    {
    /* I just now created the file.  Now I'll lock it. */

    /* But first I'll deliberately create a race condition!! */
    deliberately_fork_another_process_that_handles_file(path);

    int code = flock(fd,LOCK_EX);
    if (code < 0)
        {
        perror("flock");
        exit(1);
        }

    /* I now have the exclusive lock.  I can write to the file at will --
    or CAN I??  See below. */
    write_to_the_file_at_will(fd);
    }

显然在现实生活中,我永远不会故意创建那种竞争条件,但是在真实系统中,它的等效物可能会因为意外而发生。例如,另一个进程可能会打开文件进行读取,在其上获得共享锁并读取文件。它将看到一个空文件。这可能意味着写操作正在进行中,但也可能意味着文件为空,这是正确和最终的答案。

如果不允许空文件,则读取器可以简单地表现得像文件丢失一样。毕竟,如果读取器早了一毫秒,它无论如何都无法打开文件。在这种情况下,读取器需要在打开文件后检查文件是否为空。

如果允许空文件,则您会陷入困境,我没有准备好的答案。

我所面临的问题是,当文件第一次创建时,我想将某些默认值写入其中,因为我想在不必预先创建可能需要的每个文件的情况下“自动初始化”新系统。处理该文件的其他进程可能已经对其进行了初始化!我所知道的是,在此期间可能还有三个其他进程运行并更改了该值。在这种情况下,我肯定不希望在获取独占锁之后“随意写入文件”,因为我将覆盖所有这些更改。
我想答案是我的代码应确保文件为空才能写入它。如果它不是空的,则代码应像文件已经存在一样运行:即,它应该调用:
the_file_already_exists(fd);

也许所有这些讨论的底线是,以任何方式处理文件的每个进程都应该检查它是否为空,并相应地进行操作。但是,如果允许空文件,则我还无法想出任何保证的解决方案。如果有一种方法可以创建文件并将其锁定为单个原子序列,那么所有这些都是不必要的,但我认为没有任何方法可以做到这一点。

1
一个小细节:可以锁定父目录本身。只需像其他文件一样只读打开目录并获取该句柄的独占锁即可。只要每个人都遵守规则,您现在可以在拥有锁时随意创建和删除文件。缺点是锁定父目录不如锁定单个文件细粒度。 - Patrick Chkoreff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接