如何让python mmap赋值原子?

时间:2018-03-22 14:20:18

标签: python

如何让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不是原子的。无论如何要使它成为原子?

2 个答案:

答案 0 :(得分:3)

一般来说,你不能。无论是通过mmap还是写入,文件写入都不是原子的。一些文件系统,例如Tahoe-LAFS,确实有文件放置操作,但即便如此,它也是已知完成的问题,而不是原子操作(块是单独存储的)。文件内容更新的原子性经常使用三种方法完成:

  1. 使用rename调用,您可以确定名称指向旧文件或新文件(Python Path.replace可能更清晰)。这是用于例如maildir

  2. 使用file locks。这些通常是合作的,这意味着访问该文件的所有程序必须始终使用相同的锁定方法。有时这是不可能的,例如在某些网络文件系统中。由于这种不一致性,还使用了其他锁定方法,例如lock files - 因此"相同的方法"需求。

  3. 使用由于底层架构(例如磁盘扇区)而具有原子性的较小访问。这例如是完成的。在SQLite's journal headers。值得注意的是,阈值与mmap不同,因为内存页面本身可能是共享的,允许更精细的粒度用于原子访问(可能是CPU字大小或单字节)。

  4. 主题相当复杂。将任何这些同步方法与mmap组合的关键是mmap.flush

答案 1 :(得分:1)

我不认为这是mmap问题 - 我敢打赌它发生了,因为f.close()只是保证Python已将数据发送到底层操作系统&# 39; s缓冲但但并不意味着它已被实际写入。然后当你再次打开它并将句柄交给mmap时,你仍然在缓冲区上操作。

您可以在关闭文件之前尝试同步缓冲区,以确保所有内容都已写入:

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()不是100%保证,但是来自基础fsync()手册页:

  

调用fsync()不一定能确保包含该文件的目录中的条目也已到达磁盘。为此,还需要在目录的文件描述符上显式的fsync()。

但我敢打赌,在非常罕见的边缘情况下,它不会做你需要的。