Python多线程读取时如何安全调整mmap映射大小?
关于mmap扩容时并发读取的安全性问题
1. 并发读取与SIGBUS风险分析
你的代码中,扩容操作遵循先截断文件至更大尺寸,再调整mmap映射大小的顺序,这是正确的:
- 当文件被
truncate扩容后,操作系统会为新增的文件区域分配存储空间(或标记为稀疏文件的有效区域),此时再调用mmap.resize扩展映射范围,不会导致原有映射区域失效。 - 对于正在读取原有映射区域(扩容前的范围)的并发线程,不会触发SIGBUS——这些页面的映射关系依然有效,数据访问不受扩容操作影响。
- 若有线程尝试读取新增区域(扩容后的范围),在
mmap.resize完成前,Python的mmap对象会因访问超出当前映射大小而抛出IndexError,而非SIGBUS。这是因为Python的mmap封装会在切片访问时校验边界,不会直接触发底层操作系统的内存错误。
需要注意:你的read_bytes方法未校验offset + length是否超出当前__capacity,建议添加边界校验以避免非法访问:
def read_bytes(self, offset: int, length: int) -> bytes: assert self.__mmap is not None end = offset + length if end > self.__capacity: raise ValueError(f"Read out of bounds: {end} exceeds capacity {self.__capacity}") return self.__mmap[offset:end]
2. mmap.resize的线程安全性
Python官方文档未明确标注mmap.resize为线程安全,但结合你的实现:
- 扩容操作(
_ensure_capacity_locked)仅在持有self._lock的情况下执行,确保同一时间只有一个线程能触发扩容,避免了多个线程同时调用resize的冲突。 - 并发读取操作不会修改
mmap对象的状态,仅进行读取,因此与扩容操作的冲突风险极低——原有映射区域的读取不受影响,新增区域的读取要么在resize完成后合法访问,要么在之前触发边界错误。
3. 额外优化建议
- 引用计数的正确使用:你的
_refcount字段需要在读取操作开始时递增,结束时递减,确保release方法仅在无活跃读写线程时调用,避免资源释放导致的访问错误。 - 写入操作的原子性:当前写入操作在持有锁的情况下执行,确保了写入的原子性,且
release中调用flush的逻辑合理,能保证数据持久化。 - 稀疏文件的处理:若操作系统支持稀疏文件,
truncate扩容不会立即分配磁盘空间,这对存储效率友好,Python的mmap写入会自动触发后续的空间分配,无需额外处理。
内容的提问来源于stack exchange,提问作者Akram Khan
相关产品推荐
相关产品推荐

