如何使Python的mmap赋值操作具有原子性?

4
我如何使Python的mmap分配原子化?这里没有提到原子性:https://docs.python.org/3.0/library/mmap.html
huge_list1 = [888 for _ in range(100000000)]
huge_list2 = [9999 for _ in huge_list1]
b1 = struct.pack("100000000I", *huge_list1)
b2 = struct.pack("100000000I", *huge_list1)

f = open('mmp', 'wb')
f.write(b1)
f.close()
f = open('mmp', 'r+')
m = mmap.mmap(f.fileno(), 0)
m[:]=b2

我立即在另一个进程中执行以下代码

f = open('mmp', 'r')
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
mm = m[:]
l = struct.unpack("100000000I", mm)
set(l)

我看到的是{888, 9999}

这意味着mmap不是原子操作。有没有办法使它变成原子操作?


1
除了通常的编写其他文件并重命名覆盖之外,我不确定是否还有其他方法。我想您是否可以使用锁定来代替原子操作? - Ry-
一个小问题 - b2 的赋值应该是:b2 = struct.pack("100000000I", *huge_list2),按照目前的方式,9999 不可能以任何情况写入文件。 - zwer
附注:数组支持缓冲区接口以及fromfile/tofile,并且比struct更有效地处理此数据。 - Yann Vernier
2个回答

3
通常情况下,你无法做到。无论是通过mmap还是write进行的文件写入都不是原子操作。一些文件系统(例如Tahoe-LAFS)确实有文件放置操作,但即使在那里,也只是已知完成而不是原子操作(块被单独存储)。文件内容更新的原子性通常使用以下三种方法实现:
  1. 使用rename调用,您可以确保名称指向旧文件或新文件之一(Python的Path.replace可能更清晰)。例如,在maildir中使用此方法。

  2. 使用文件锁定。这些通常是协作的,意味着访问文件的所有程序必须始终使用相同的锁定方法。有时,这是不可能的,例如跨某些网络文件系统。由于这种不一致性,也使用其他锁定方法,例如锁定文件-因此需要“相同的方法”要求。

  3. 使用较小的访问,由于底层架构(如磁盘扇区)而具有原子性。例如在SQLite的日志头中执行此操作。值得注意的是,由于内存页面本身可以共享,因此mmap的阈值不同,允许更细粒度的原子访问(可能是CPU字大小或单个字节)。

这个话题相当复杂。将任何一种同步方法与mmap结合的关键是mmap.flush


1
我认为这不是一个问题 - 我敢打赌,它发生的原因是因为仅保证Python已将数据发送到底层操作系统的缓冲区,但这并不意味着它已经被实际写入。然后当您再次打开它,并将句柄传递给时,您仍在操作缓冲区。
您可以尝试在关闭文件之前同步缓冲区,以确保所有内容都已被写入:
import os

f = open('mmp', 'wb')
f.write(b1)
f.flush()
os.fsync(f.fileno()) 
f.close()

或者更好的是,让Python在出现错误时自动处理关闭:

with open('mmp', 'wb') as f:
    f.write(b1)
    f.flush()
    os.fsync(f.fileno())

尽管即使使用os.fsync()也不能百分之百保证,但从底层的fsync()手册中可以看出:

调用fsync()并不一定能确保文件所在目录的条目已经到达磁盘。为此,还需要对目录的文件描述符进行显式的fsync()。

但我敢打赌,在非常罕见的边缘情况下,它也无法满足您的需求。


我认为提问者正在同时运行这些代码片段。 - user2357112
@user2357112 - 我理解他的问题是,他遇到了一个数据交错的问题,因为他的初始文件写入与mmap写入混合在一起,这可以从外部进程中观察到。 - zwer

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