我正在尝试在python 3x和Linux / macOS中实现“记录管理器”类。该类相对简单明了,我唯一想要的“困难”是能够在多个进程中访问保存结果的同一个文件。
从概念上看,这似乎很容易:在保存时,在文件上获取独占锁。更新您的信息,保存新信息,释放文件上的独占锁。很容易。
我使用fcntl.lockf(file, fcntl.LOCK_EX)
来获取独占锁。问题是,通过在互联网上查找,我发现有很多不同的网站都说这不可靠,在Windows上无法工作,在NFS上的支持也不稳定,并且在macOS和Linux之间的情况可能会发生变化。
我已经接受了代码在Windows上无法工作,但是我希望能够使其在macOS(单台机器)和Linux(在具有NFS的多个服务器上)上工作。
问题是我似乎无法使此功能正常工作;并且在调试了一段时间之后,在macOS上测试通过之后,当我在具有linux(ubuntu 16.04)的NFS上尝试它们时,它们失败了。问题是多个进程保存的信息之间存在不一致 - 有些进程的修改丢失了,这意味着在锁定和保存过程中出现了问题。
我确定我做错了某件事情,并且我怀疑这可能与我在网上阅读的问题有关。那么,在macOS和Linux上通过NFS处理对同一文件的多次访问的正确方法是什么?
编辑
下面是将新信息写入磁盘的典型方法:
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
虽然这是一个典型的仅从磁盘读取信息的方法:
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
此外,这就是_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
错误信息
我编写了一个单元测试,创建了100个不同的进程,其中50个读取并且50个写入同一个文件。每个进程都会进行一些随机等待以避免按顺序访问文件。
问题在于某些记录没有被保留;最终会有3-4个随机记录丢失,因此我最后只得到46-47条记录而不是50条。
编辑2
我已修改上述代码,现在我在一个单独的锁文件上获取锁,而不是在文件本身上获取锁。这可以防止关闭文件会释放锁(如@janneb所建议的那样),并使代码在mac上正确运行。然而,在带有NFS的linux上,相同的代码会失败。
'rb+'
静默失败,无法首先获得此独占锁,而只是继续进行而不被阻塞等待。 - Darkonaut