社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python开发者必学:mmap模块如何让你的代码运行速度提升数倍

python • 3 月前 • 114 次点击  

共享内存是一种高效的进程间通信(IPC)机制,它允许多个进程访问同一块内存区域。与其他IPC方式相比,共享内存的最大优势在于其高效性—数据无需在进程间复制,所有进程可以直接访问同一块内存区域,这使得共享内存成为需要大量数据交换场景的理想选择。

在Python中,标准库提供了mmap模块,它封装了内存映射文件的功能,可用于实现共享内存。内存映射文件是一种将文件内容映射到进程地址空间的机制,使得文件访问变得像内存访问一样高效。当多个进程映射同一个文件时,它们实际上共享了同一块内存区域,从而实现了共享内存的功能。

mmap模块基础

mmap模块是Python标准库的一部分,提供了内存映射文件的支持。通过mmap,可以将文件映射到内存中,像操作内存一样操作文件,极大提高了文件操作的效率。

import mmap
import os

# 创建一个临时文件并写入数据
file_size = 1024# 1KB
with open('example.dat''wb'as f:
    f.write(b'\x00' * file_size)  # 用零填充文件

# 打开文件并创建内存映射
with open('example.dat''r+b'as f:
    # 创建内存映射对象
    mm = mmap.mmap(f.fileno(), 0)
    
    # 写入数据
    mm.write(b'Hello, mmap!')
    
    # 重置位置指针
    mm.seek(0)
    
    # 读取数据
    print(mm.read(13))  # 输出: b'Hello, mmap!'
    
    # 关闭内存映射
    mm.close()

# 清理临时文件
os.unlink('example.dat')

在这个例子中,首先创建了一个大小为1KB的临时文件,然后使用mmap将其映射到内存中。通过内存映射对象,我们可以直接读写文件内容,就像操作内存一样方便。

使用mmap实现进程间共享内存

mmap模块不仅可用于高效访问文件,还可实现进程间共享内存。当多个进程映射同一个文件时,它们实际上共享了同一块内存区域,从而实现了共享内存的功能。

import mmap
import os
import time
from multiprocessing import Process

def child_process(shared_mem_name):
    # 子进程打开共享内存
    with open(shared_mem_name, 'r+b'as f:
        # 创建内存映射
        mm = mmap.mmap(f.fileno(), 0)
        
        # 从共享内存读取数据
        mm.seek(0)
        print(f"子进程读取:{mm.readline().decode().strip()}")
        
        # 向共享内存写入数据
        mm.seek(0)
        mm.write(b"Hello from child process!\n")
        mm.flush()  # 确保数据被写入
        
        # 关闭内存映射
        mm.close()

def parent_process():
    # 创建共享内存文件
    shared_mem_name = 'shared_memory.dat'
    
    # 创建并初始化文件
    with open(shared_mem_name, 'wb'as f:
        f.write(b"Hello from parent process!\n")
        f.write(b"\x00" * 1024)  # 保证足够空间
    
    # 创建子进程
    p = Process(target=child_process, args=(shared_mem_name,))
    p.start()
    
    # 父进程打开共享内存
     with open(shared_mem_name, 'r+b'as f:
        # 创建内存映射
        mm = mmap.mmap(f.fileno(), 0)
        
        # 等待子进程写入数据
        time.sleep(1)
        
        # 从共享内存读取数据
        mm.seek(0)
        print(f"父进程读取:{mm.readline().decode().strip()}")
        
        # 关闭内存映射
        mm.close()
    
    # 等待子进程结束
    p.join()
    
    # 清理共享内存文件
    os.unlink(shared_mem_name)

if __name__ == "__main__":
    parent_process()

在这个例子中,父进程创建并初始化了一个文件作为共享内存,创建了一个子进程。父进程和子进程都将这个文件映射到了内存中,从而共享了同一块内存区域。子进程从共享内存中读取了父进程的消息,向共享内存写入自己的消息。父进程等待子进程写入完成后,再从共享内存中读取子进程的消息。

通过这种方式,不同进程可以高效地共享大量数据,而无需进行进程间的数据复制,从而提高了多进程应用的性能。

内存映射的高级特性

1、内存映射的访问模式

在创建内存映射时,可以指定不同的访问模式:

import mmap

# 只读模式
mm_r = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

# 读写模式(默认)
mm_rw = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)

# 写拷贝模式(修改不影响原文件)
mm_c = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY)

这些访问模式可以根据需求选择使用。例如,当只需要读取数据时,可以使用只读模式;需要读写数据时,使用读写模式;需要修改数据但不希望影响原文件时,使用写拷贝模式。

2、内存映射的锁定和刷新

在多进程环境中,为了防止多个进程同时修改共享内存而导致数据不一致,可以使用锁定功能,并通过刷新确保数据被写入磁盘:

import mmap

with open('shared_memory.dat''r+b'as f:
    mm = mmap.mmap(f.fileno(), 0)
    
    # 锁定一部分内存区域
    mm.seek(0)
    mm.mlock(0100)  # 锁定前100个字节
    
    # 在锁定区域读写数据
    mm.write(b"Protected data")
    
    # 刷新到磁盘
    mm.flush()
    
    # 解锁
    mm.munlock(0 100)
    
    mm.close()

通过锁定和解锁操作,可以确保在多进程环境中安全地访问共享内存。调用flush方法可以确保内存映射的修改被写入磁盘,使其他进程能够看到最新的数据。

实际应用场景

1、大文件处理

当需要处理大文件时,使用传统的文件读写方式可能会占用大量内存,而使用mmap可以有效地减少内存使用:

import mmap
import re

def search_in_large_file(file_path, pattern):
    with open(file_path, 'r+b'as f:
        # 创建内存映射
        mm = mmap.mmap(f.fileno(), 0)
        
        # 搜索模式
        pattern = re.compile(pattern.encode())
        
        # 查找所有匹配
        matches = []
         for match in pattern.finditer(mm):
            start, end = match.span()
            matches.append((start, mm[start:end].decode()))
        
        # 关闭内存映射
        mm.close()
        
        return matches

# 示例:在大文件中搜索所有包含"Python"的行
matches = search_in_large_file('large_file.txt'r'.*Python.*')
for pos, text in matches:
    print(f"在位置 {pos} 找到: {text}")

通过使用mmap,可以高效地处理大文件,而不必担心内存不足的问题。

2、进程间通信

mmap是一种高效的进程间通信方式,特别适合需要共享大量数据的场景。

以下是一个生产者-消费者模型的简化实现:

import mmap
import struct
import time
from multiprocessing import Process

def producer(shared_mem_name):
    with open(shared_mem_name, 'r+b'as f:
        mm = mmap.mmap(f.fileno(), 0)
        
        # 向共享内存写入数据
        for i in range(5):
            # 格式:消息计数器 + 消息内容
            message = f"Message  {i}".encode()
            mm.seek(0)
            mm.write(struct.pack('I', i))  # 写入4字节计数器
            mm.write(message)  # 写入消息内容
            mm.flush()
            
            print(f"生产者写入: {message.decode()}")
            time.sleep(0.5)
        
        # 发送结束信号
        mm.seek(0)
        mm.write(struct.pack('I'-1))  # -1表示结束
        mm.flush()
        
        mm.close()

def consumer(shared_mem_name):
    with open(shared_mem_name, 'r+b'as f:
        mm = mmap.mmap(f.fileno(), 0)
        
        # 从共享内存读取数据
        last_counter = -1
        whileTrue:
            mm.seek(0)
            counter = struct.unpack('I', mm.read(4))[0]  # 读取计数器
            
            # 检查是否有新消息
            if counter != last_counter:
                if counter == -1:  # 结束信号
                    break
                
                # 读取消息内容
                message = mm.read(20).strip(b'\x00').decode()
                print(f"消费者读取: {message}")
                
                last_counter = counter
            
            time.sleep(0.1)
        
        mm.close()

这个例子实现了一个简单的生产者-消费者模型,生产者向共享内存写入消息,消费者从共享内存读取消息。这种方式相比于其他IPC方式,可以更高效地传输大量数据。

注意事项及最佳实践

  1. 文件大小:在Windows上,内存映射文件的大小不能为0;在Unix/Linux上,虽然可以映射大小为0的文件,但这样做没有实际意义。在创建内存映射前,应确保文件大小足够。

  2. 访问模式:根据实际需求选择合适的访问模式。如果只需要读取数据,使用ACCESS_READ可以提高安全性。

  3. 同步问题:在多进程环境中使用共享内存时,需要注意同步问题。可以使用进程锁、信号量等机制确保数据一致性。

  4. 资源释放:使用完mmap对象后,应及时调用close方法释放资源。最好使用with语句自动管理资源。

  5. 大小限制:内存映射受到虚拟地址空间的限制。在32位系统上,单个映射的大小通常不能超过2GB。

下面是一个使用上下文管理器安全管理mmap资源的例子:

import mmap
import os
import contextlib

@contextlib.contextmanager
def mmap_context(filename, size=0):
    """使用上下文管理器管理内存映射资源"""
    # 创建或打开文件
    ifnot os.path.exists(filename):
        with open(filename, 'wb'as f:
            if size > 0:
                f.write(b'\x00' * size)
            else:
                f.write(b'\x00')  # 至少写入一个字节
    
    # 打开文件并创建内存映射
    file = open(filename, 'r+b')
    try:
        mm = mmap.mmap(file.fileno(), 0)
        yield mm
    finally:
        mm.close()
        file.close()

# 使用示例
with mmap_context('example.dat'1024as mm:
    mm.write(b'Hello, mmap!')
    mm.seek(0)
    print(mm.read(13).decode())  # 输出: Hello, mmap!

通过使用上下文管理器,可以确保内存映射资源在使用完毕后被正确释放,避免资源泄漏。

总结

Python的mmap模块提供了内存映射文件的功能,可用于实现高效的文件访问和进程间共享内存。通过将文件映射到内存中,可以像操作内存一样操作文件,提高了文件操作的效率。当多个进程映射同一个文件时,它们实际上共享了同一块内存区域,从而实现了共享内存的功能。

如果你觉得文章还不错,请大家 点赞、分享、留言 下,因为这将是我持续输出更多优质文章的最强动力!


我们还为大家准备了Python资料,感兴趣的小伙伴快来找我领取一起交流学习哦!

图片

往期推荐

历时一个月整理的 Python 爬虫学习手册全集PDF(免费开放下载)

Beautiful Soup快速上手指南,从入门到精通(PDF下载)

Python基础学习常见的100个问题.pdf(附答案)

124个Python案例,完整源代码!

30 个Python爬虫的实战项目(附源码)

从入门到入魔,100个Python实战项目练习(附答案)!

80个Python数据分析必备实战案例.pdf(附代码),完全开放下载

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/180763
 
114 次点击