- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我正在尝试在 python 3x 和 linux/macOS 中实现一个“记录管理器”类。该类(class)相对简单明了,我唯一想要的“困难”是能够在多个进程上访问同一个文件(保存结果的地方)。
从概念上讲,这似乎很简单:保存时,获取文件的独占锁。更新您的信息,保存新信息,释放文件的独占锁。很简单。
我正在使用 fcntl.lockf(file, fcntl.LOCK_EX)
获取独占锁。问题是,在互联网上,我发现 很多 不同的网站都在说这不可靠,它不能在 Windows 上运行,对 NFS 的支持不稳定,并且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
虽然这是只从磁盘读取信息的典型方法看起来像这样:
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 上失败了。
最佳答案
我不明白文件锁和 os.replace() 的组合有何意义。当文件被替换(也就是目录项被替换)时,所有已经存在的文件锁(可能包括等待加锁成功的文件锁,这里的语义不太清楚)和文件描述符都会对旧文件,不是新文件。我怀疑这是导致您在测试中丢失一些记录的竞争条件背后的原因。
os.replace() 是一种很好的技术,可以确保读者不会阅读部分更新。但它在面对多个更新程序时并不能很好地工作(除非丢失一些更新是可以的)。
另一个问题是 fcntl 是一个非常非常愚蠢的 API。特别是,锁绑定(bind)到进程,而不是文件描述符。这意味着例如指向文件的任何文件描述符上的 close() 将释放锁定。
一种方法是使用“锁定文件”,例如利用 link() 的原子性。来自 http://man7.org/linux/man-pages/man2/open.2.html :
Portable programs that want to perform atomic file locking using a lockfile, and need to avoid reliance on NFS support for O_EXCL, can create a unique file on the same filesystem (e.g., incorporating hostname and PID), and use link(2) to make a link to the lockfile. If link(2) returns 0, the lock is successful. Otherwise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.
如果可以读取稍微陈旧的数据,那么您可以仅对更新文件时使用的临时文件使用此 link() 舞蹈,然后使用 os.replace() 用于读取的“主”文件(阅读然后可以是无锁的)。如果不是,那么您需要为“主”文件执行 link() 技巧,而忘记共享/独占锁定,所有锁都是独占的。
附录:使用锁定文件时要处理的一件棘手的事情是当进程由于某种原因终止并留下锁定文件时该怎么办。如果这是在无人值守的情况下运行,您可能需要合并某种超时和删除锁定文件(例如检查 stat() 时间戳)。
关于python - 如何在 NFS 上进行正确的文件锁定?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48770531/
我正在处理一组标记为 160 个组的 173k 点。我想通过合并最接近的(到 9 或 10 个组)来减少组/集群的数量。我搜索过 sklearn 或类似的库,但没有成功。 我猜它只是通过 knn 聚类
我有一个扁平数字列表,这些数字逻辑上以 3 为一组,其中每个三元组是 (number, __ignored, flag[0 or 1]),例如: [7,56,1, 8,0,0, 2,0,0, 6,1,
我正在使用 pipenv 来管理我的包。我想编写一个 python 脚本来调用另一个使用不同虚拟环境(VE)的 python 脚本。 如何运行使用 VE1 的 python 脚本 1 并调用另一个 p
假设我有一个文件 script.py 位于 path = "foo/bar/script.py"。我正在寻找一种在 Python 中通过函数 execute_script() 从我的主要 Python
这听起来像是谜语或笑话,但实际上我还没有找到这个问题的答案。 问题到底是什么? 我想运行 2 个脚本。在第一个脚本中,我调用另一个脚本,但我希望它们继续并行,而不是在两个单独的线程中。主要是我不希望第
我有一个带有 python 2.5.5 的软件。我想发送一个命令,该命令将在 python 2.7.5 中启动一个脚本,然后继续执行该脚本。 我试过用 #!python2.7.5 和http://re
我在 python 命令行(使用 python 2.7)中,并尝试运行 Python 脚本。我的操作系统是 Windows 7。我已将我的目录设置为包含我所有脚本的文件夹,使用: os.chdir("
剧透:部分解决(见最后)。 以下是使用 Python 嵌入的代码示例: #include int main(int argc, char** argv) { Py_SetPythonHome
假设我有以下列表,对应于及时的股票价格: prices = [1, 3, 7, 10, 9, 8, 5, 3, 6, 8, 12, 9, 6, 10, 13, 8, 4, 11] 我想确定以下总体上最
所以我试图在选择某个单选按钮时更改此框架的背景。 我的框架位于一个类中,并且单选按钮的功能位于该类之外。 (这样我就可以在所有其他框架上调用它们。) 问题是每当我选择单选按钮时都会出现以下错误: co
我正在尝试将字符串与 python 中的正则表达式进行比较,如下所示, #!/usr/bin/env python3 import re str1 = "Expecting property name
考虑以下原型(prototype) Boost.Python 模块,该模块从单独的 C++ 头文件中引入类“D”。 /* file: a/b.cpp */ BOOST_PYTHON_MODULE(c)
如何编写一个程序来“识别函数调用的行号?” python 检查模块提供了定位行号的选项,但是, def di(): return inspect.currentframe().f_back.f_l
我已经使用 macports 安装了 Python 2.7,并且由于我的 $PATH 变量,这就是我输入 $ python 时得到的变量。然而,virtualenv 默认使用 Python 2.6,除
我只想问如何加快 python 上的 re.search 速度。 我有一个很长的字符串行,长度为 176861(即带有一些符号的字母数字字符),我使用此函数测试了该行以进行研究: def getExe
list1= [u'%app%%General%%Council%', u'%people%', u'%people%%Regional%%Council%%Mandate%', u'%ppp%%Ge
这个问题在这里已经有了答案: Is it Pythonic to use list comprehensions for just side effects? (7 个答案) 关闭 4 个月前。 告
我想用 Python 将两个列表组合成一个列表,方法如下: a = [1,1,1,2,2,2,3,3,3,3] b= ["Sun", "is", "bright", "June","and" ,"Ju
我正在运行带有最新 Boost 发行版 (1.55.0) 的 Mac OS X 10.8.4 (Darwin 12.4.0)。我正在按照说明 here构建包含在我的发行版中的教程 Boost-Pyth
学习 Python,我正在尝试制作一个没有任何第 3 方库的网络抓取工具,这样过程对我来说并没有简化,而且我知道我在做什么。我浏览了一些在线资源,但所有这些都让我对某些事情感到困惑。 html 看起来
我是一名优秀的程序员,十分优秀!