Python的lockf和flock行为

18
我看过足够多关于flock/lockf/fcntl之间区别的stackoverflow帖子,但我无法回答以下观察问题:
>>> import fcntl
>>> a = open('/tmp/locktest', 'w')
>>> b = open('/tmp/locktest', 'w')
>>> fcntl.lockf(a, fcntl.LOCK_EX | fcntl.LOCK_NB)
>>> fcntl.lockf(a, fcntl.LOCK_EX | fcntl.LOCK_NB)
>>> fcntl.lockf(b, fcntl.LOCK_EX | fcntl.LOCK_NB)
>>>
>>> a.close()
>>> b.close()

>>> a = open('/tmp/locktest', 'w')
>>> b = open('/tmp/locktest', 'w')
>>> fcntl.flock(a, fcntl.LOCK_EX | fcntl.LOCK_NB)
>>> fcntl.flock(a, fcntl.LOCK_EX | fcntl.LOCK_NB)
>>> fcntl.flock(b, fcntl.LOCK_EX | fcntl.LOCK_NB)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 35] Resource temporarily unavailable

这两种情况的行为为什么不同?我知道显而易见的答案是这是两种不同的锁定机制。我正在寻找:

  1. lockf()flock()实际上对文件(inode/fd)执行了什么操作?
  2. 根据演示,我们可以递归地获取相同的锁吗?

我理解fds和相关内容的基础知识,因此我更喜欢有更深入的技术答案,涉及操作系统级别的细节。

OSX 10.9.3,Python:2.7.5


1
我已经趁机编辑了这个问题,确保它包含一个实际的问题。请查看并编辑如果您不是在询问这个问题。谢谢。 - NPE
@NPE:虽然它是我第一句话的一部分,但最好明确地放在那里。谢谢 :) - Jatin Kumar
1个回答

14

一篇关于此事的好文章: 论文件锁的不完善之处

简言之:

  • POSIX锁:

    lockf()大多数情况下只是fcntl()接口的实现

    fcntl()锁与进程绑定,而非文件描述符。如果一个进程对某个文件有多个打开的文件描述符,在这些文件描述符中使用任何一个来获取锁定将重置锁定。

  • BSD锁:

    flock()锁与文件描述符绑定,而非进程。

此外

带有测试的良好分析: 咨询文件锁 - 我对 POSIX 和 BSD 锁的看法

摘自总结:

  • fcntl和flock风格的锁完全彼此正交。提供这两种锁的任何系统(Linux都有)将独立地处理通过每种锁获得的锁。
  • 在进程退出或异常终止时,同样会自动释放 POSIX 和 BSD 锁。
  • 当进程设置了 FD_CLOEXEC 标志,强制关闭文件描述符并且不被新进程继承时,同时也不能保留 POSIX 和 BSD 锁。

5
公正的评估。需要强调的是,在多线程情况下,POSIX锁在默默失败,并且(可能)应该被认为对于现代软件堆栈来说是有害的,尽管存在GIL。更为重要的是,需要强调的是,无论是POSIX还是BSD锁定组或全局可读取文件都会带来极高的安全风险。为什么?因为具有文件读取权限的任何用户都可以通过先获取锁定然后永远不释放来永久死锁你的Python应用程序。 - Cecil Curry
5
tl;dr: 在处理文件锁的时候,比起BSD或POSIX风格的锁,内存锁(例如multiprocessing模块提供的同步原语)更加推荐使用。如果必须使用文件锁,那么使用BSD风格的flock锁要比POSIX风格的fcntl锁更好。不过,无论是BSD还是POSIX风格的锁,只有当被锁定的文件的权限严格限制在最多为0600时,才应该被使用。 - Cecil Curry
我想补充一点,就是请注意你正在使用 fcntl.LOCK_NB 标志。如果你移除这个标志,那么你的第二种情况将会阻塞而不是抛出异常。 - Droid Coder
另外,如果您在另一个窗口中运行另一个Python会话,并对文件使用lockf()并保持文件打开,则会发现在第一次调用fcntl.lockf()时,在第二个会话中发生异常(如果我们回到使用fcntl.LOCK_NB)。这证实了fcntl.lockf()的状态是进程全局的,而fcntl.flock()的状态是由文件描述符作用域确定的。 - Droid Coder
1
关于@CecilCurry的链接,需要注意的是Python multiprocessing模块仅适用于由共同父进程生成的Python子进程的同步,如果其中一个进程崩溃这会泄漏信号量和共享内存段,并且破坏队列对象管道 - Stéphane Gourichon

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