Ant*_*Ant 13 python linux nfs locking multiprocessing
我正在尝试在python 3x和linux/macOS中实现"记录管理器"类.这个类相对容易和直接,我想要的唯一"硬"的东西是能够在多个进程上访问相同的文件(保存结果).
从概念上讲,这似乎很简单:保存时,获取文件的独占锁.更新您的信息,保存新信息,释放文件的独占锁定.很容易.
我正在使用fcntl.lockf(file, fcntl.LOCK_EX)获得独家锁.问题是,在网上看,我发现很多不同的网站都说这不可靠,它在Windows上不起作用,对NFS的支持是不稳定的,并且macOS和macOS之间的事情可能会发生变化. Linux操作系统.
我已经接受了代码不能在Windows上运行,但我希望能够在macOS(单机)和linux(在多个NFS服务器上)上运行.
问题是我似乎无法做到这一点; 经过一段时间的调试和在macOS上传递测试后,一旦我在使用linux(ubuntu 16.04)的NFS上尝试它们,它们就失败了.问题是多个进程保存的信息之间存在不一致 - 某些进程缺少修改,这意味着锁定和保存过程出现问题.
我相信,有什么我做错了,我怀疑这可能与我在网上读到有关的问题.那么,通过NFS处理在macOS和linux上运行的同一文件的多个访问权限的正确方法是什么?
编辑
这就是将新信息写入磁盘的典型方法如下:
sf = open(self._save_file_path, 'rb+')
try:
fcntl.lockf(sf, fcntl.LOCK_EX) # acquire an exclusive lock - only one writer
self._raw_update(sf) #updates the records from file (other processes may have modified it)
self._saved_records[name] = new_info
self._raw_save() #does not check for locks (but does *not* release the lock on self._save_file_path)
finally:
sf.flush()
os.fsync(sf.fileno()) #forcing the OS to write to disk
sf.close() #release the lock and close
Run Code Online (Sandbox Code Playgroud)
虽然这是仅从磁盘读取信息的典型方法如下所示:
sf = open(self._save_file_path, 'rb')
try:
fcntl.lockf(sf, fcntl.LOCK_SH) # acquire shared lock - multiple writers
self._raw_update(sf) #updates the records from file (other processes may have modified it)
return self._saved_records
finally:
sf.close() #release the lock and close
Run Code Online (Sandbox Code Playgroud)
另外,这就是_raw_save的样子:
def _raw_save(self):
#write to temp file first to avoid accidental corruption of information.
#os.replace is guaranteed to be an atomic operation in POSIX
with open('temp_file', 'wb') as p:
p.write(self._saved_records)
os.replace('temp_file', self._save_file_path) #pretty sure this does not release the lock
Run Code Online (Sandbox Code Playgroud)
错误信息
我编写了一个单元测试,我创建了100个不同的进程,50个读取,50个写入同一个文件.每个进程都会随机等待,以避免顺序访问文件.
问题是有些记录没有保存; 最后有3-4个随机记录缺失,所以我最终只得到46-47记录而不是50记录.
编辑2
我修改了上面的代码,我获取的锁不在文件本身上,而是在单独的锁文件上.这可以防止关闭文件的问题会释放锁(如@janneb所示),并使代码在mac上正常工作.但是在使用NFS的linux上,相同的代码失败了.
我看不到文件锁和os.replace()的组合如何有意义。替换文件(即替换目录条目)后,所有现有文件锁(可能包括等待锁定成功的文件锁,我不确定此处的语义),并且文件描述符将与旧文件,而不是新文件。我怀疑这是比赛条件背后的原因,导致您在测试中丢失一些记录。
os.replace()是一种很好的技术,可确保读者不会阅读部分更新。但是,面对多个更新程序,它不能正常运行(除非丢失某些更新是可以的)。
另一个问题是fcntl是一个非常愚蠢的API。特别是,锁绑定到进程,而不是文件描述符。这意味着,例如,指向该文件的任何文件描述符上的close()都会释放该锁。
一种方法是使用“锁定文件”,例如,利用link()的原子性。从http://man7.org/linux/man-pages/man2/open.2.html:
想要使用锁定文件执行原子文件锁定并且需要避免依赖NFS对O_EXCL的支持的可移植程序可以在同一文件系统上创建唯一文件(例如,合并主机名和PID),并使用link(2)进行创建锁定文件的链接。如果link(2)返回0,则锁定成功。否则,对唯一文件使用stat(2)来检查其链接计数是否已增加到2,在这种情况下,锁定也成功。
如果可以读取稍有陈旧的数据,则可以仅对在更新文件时使用的临时文件使用此link()跳舞,然后对用于读取的“主”文件os.replace()进行使用(然后可以进行读取)无锁)。如果不是,那么您需要对“主”文件执行link()技巧,而忘记共享/独占锁定,那么所有锁都是独占的。
附录:使用锁定文件时要处理的一件棘手的事情是,由于某种原因导致进程死机并留下锁定文件时该怎么办。如果要在无人值守的情况下运行,则可能需要合并某种超时和删除锁定文件(例如,检查stat()时间戳)。
使用随机命名的硬链接和这些文件上的链接计数作为锁定文件是一种常见策略(例如this),并且可以说比使用更好,lockd但有关 NFS 上各种锁定的限制的更多信息,请阅读以下内容: http:// /0pointer.de/blog/projects/locking.html
您还会发现,对于Mbox通过 NFS 使用文件的 MTA 软件来说,这是一个长期存在的标准问题。可能最好的答案是使用Maildir而不是Mbox,但是如果您在 postfix 之类的源代码中查找示例,它将接近最佳实践。如果他们根本无法解决这个问题,那也可能是你的答案。