Python多进程(2)——mmap模块与mmap对象

本文介绍了Python中的mmap模块及mmap对象的使用方法,包括如何创建mmap对象、mmap对象与字符串对象的区别以及mmap对象提供的各种方法。此外,还提供了一个通过mmap实现实时文件监控的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  本文介绍Python mmap模块与mmap对象的用法。

  mmap 模块提供“内存映射的文件对象”,mmap 对象可以用在使用 plain string 的地方,mmap 对象和 plain string 的区别是:

  • mmap 对象不提供字符串对象的方法;
  • mmap 对象是可变的,而 str 对象是不可变的
  • mmap 对象同时对应于打开的文件,多态于一个Python file 对象

  mmap 对象可以切片和索引,也可以为它的切片或索引赋值(因为 mmap 对象是可变的),为 mmap 对象的切片赋值时,赋值语句右值的长度必须和左值切片的长度相同。mmap 对象可以作为进程间通过文件进行 IPC 的一种替换手段。

 

创建 mmap 对象

mmap(filedesc, length, tagname='') #windows
mmap(filedesc, length, flag=MAP_SHARED, prot=PROT_READ|PROT_WRITE) #Unix

  创建并返回一个 mmap 对象,参数 filedesc 通常是由 f.fileno()获得的,这在Python文件系列中已经介绍过。

  mmap 创建对象的含义是:将指定 fd 的前 length 字节映射到内存。

  Windows中,可以通过参数tagname为一段内存映射指定名称,这样一个文件上面可以同时具有多个 mmap。windows中的内存映射都是可读可写的,同时在进程之间共享。

  Unix平台上,参数 flags 的可选值包括:

    mmap.MAP_PRIVATE:这段内存映射只有本进程可用;

    mmap.MAP_SHARED:将内存映射和其他进程共享,所有映射了同一文件的进程,都能够看到其中一个所做的更改;

  参数 prot 对应的取值包括:mmap.PROT_READ, mmap.PROT_WRITE 和 mmap.PROT_WRITE | mmap.PROT_READ。最后一者的含义是同时可读可写。

 

mmap 对象的方法

 m.close()   关闭 m 对应的文件;

 m.find(str, start=0)   从 start 下标开始,在 m 中从左往右寻找子串 str 最早出现的下标;

 m.flush([offset, n])   把 m 中从offset开始的n个字节刷到对应的文件中,参数 offset 要么同时指定,要么同时不指定;

 m.move(dstoff, srcoff, n)   等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节,可能会覆盖重叠的部分。

 m.read(n)   返回一个字符串,从 m 对应的文件中最多读取 n 个字节,将会把 m 对应文件的位置指针向后移动;

 m.read_byte()   返回一个1字节长的字符串,从 m 对应的文件中读1个字节,要是已经到了EOF还调用 read_byte(),则抛出异常 ValueError;

 m.readline()   返回一个字符串,从 m 对应文件的当前位置到下一个'\n',当调用 readline() 时文件位于 EOF,则返回空字符串;

 m.resize(n)   把 m 的长度改为 n,m 的长度和 m 对应文件的长度是独立的;

 m.seek(pos, how=0)   同 file 对象的 seek 操作,改变 m 对应的文件的当前位置;

 m.size()   返回 m 对应文件的长度(不是 m 对象的长度len(m));

 m.tell()   返回 m 对应文件的当前位置;

 m.write(str)   把 str 写到 m 对应文件的当前位置,如果从 m 对应文件的当前位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError

 m.write_byte(byte)   把1个字节(对应一个字符)写到 m 对应文件的当前位置,实际上 m.write_byte(ch) 等于 m.write(ch)。如果 m 对应文件的当前位置在 m 的结尾,也就是 m 对应文件的当前位置到 m 结尾剩余的空间不足1个字节,write() 抛出异常ValueError,而 write_byte() 什么都不做。

  对于EOF的处理,write() 和 read_byte() 抛出异常 ValueError,而 write_byte() 和 read() 什么都不做。

例:

# process 1
f = open('xxx', 'w')
while True:
    data = raw_input('Enter some text:')
    f.seek(0)
    f.write(data)
    f.truncate()
    f.flush()

# process 2
import mmap, os, time
m = mmap.mmap(os.open('xxx', os.O_RDWR), 1)
last = None
while True:
    m.resize(m.size())
    data = [:]
    if data != last:
        print data
        last = data
    time.sleep(5)

  该例子中,process 1 等待用户输入新内容并将其写入到文件 xxx 中,process 2 直接将整个文件映射到内存对象 m,然后每隔5秒检查一下文件是否发生变化。

<think>好的,用户现在问的是在Linux平台下,如何在使用Pythonmmap进行多进程共享内存时加锁。之前用户已经询问过类似的问题,关于如何在Python中实现多进程共享内存的原子操作,我给出了使用multiprocessing模块和共享内存的方法,以及锁机制。现在用户具体到了mmap的使用,并且希望了解如何在这样的场景下加锁。 首先,我需要确定用户的需求。用户可能正在使用mmap模块创建共享内存,并且需要确保多个进程在访问共享数据时不会发生冲突。他们可能已经了解mmap的基本用法,但需要知道如何正确同步进程间的操作,尤其是加锁机制。 接下来,我应该回顾mmapPython中的使用方式。mmap模块允许将文件映射到内存,支持跨进程的共享内存。但是,mmap本身并不提供锁机制,所以需要额外的同步手段。在之前的回答中,我提到了使用multiprocessing的Lock,但这里用户可能希望直接在mmap的场景下使用锁。 需要考虑Linux平台下的锁机制。通常,进程间锁可以通过文件锁(fcntl.flock或posix_ipc)或者使用信号量(如posix_ipc.Semaphore)。不过,Python标准库中的multiprocessing模块提供的锁可能无法直接用于mmap,因为它们是基于进程的,而mmap可能涉及更底层的共享内存。 用户可能需要一个示例,展示如何在mmap共享内存中使用锁。这里可能需要使用到fcntl模块来实现文件锁,或者使用共享内存中的某个位置作为锁变量,结合原子操作。但原子操作在Python中比较受限,可能需要依赖ctypes调用系统函数,如之前的回答提到的__sync_lock_test_and_set。 不过,使用文件锁可能更简单,尽管性能可能不如原子操作。另外,还可以考虑在共享内存中预留一部分作为锁标志,结合信号量或者其他同步机制。例如,使用一个共享的整型变量作为锁,通过原子操作来获取和释放。 需要指出的是,正确实现进程间锁需要考虑死锁、竞争条件等问题。因此,推荐使用现有的成熟机制,如posix_ipc库提供的信号量,或者使用文件锁。此外,Python的multiprocessing模块的Lock可能无法跨进程工作,如果进程不是由同一父进程fork出来的,可能需要使用系统级的锁。 在示例中,可能需要展示如何使用fcntl.flock来对文件进行加锁,因为mmap通常文件映射相关联。或者,使用共享内存中的某个位置作为锁,通过原子操作来修改它。例如,在共享内存中分配一个字节作为锁标志,使用fcntl或原子操作来确保操作的原子性。 但需要注意,Python中的普通赋值操作不是原子的,因此在没有锁的情况下直接修改共享内存变量可能导致竞争条件。因此,必须使用某种同步机制,无论是文件锁、信号量,还是基于共享内存的原子操作(通过调用底层函数)。 总结来说,回答需要涵盖以下几点: 1. 使用mmap创建共享内存的方法。 2. 如何在该共享内存区域上加锁,可能的选项包括文件锁、信号量、或原子操作。 3. 提供具体的代码示例,展示如何实现这些锁机制。 4. 强调注意事项,如锁的正确释放、性能影响、跨平台问题等。 需要确保示例代码正确,并且适合Linux平台。可能还需要比较不同方法的优缺点,帮助用户根据需求选择合适的方式。</think>在 Linux 平台下,使用 Python 的 `mmap` 实现多进程共享内存时,可以通过 **文件锁(`fcntl.flock`)** 或 **共享内存中的原子标记** 实现同步。以下是具体方法和示例: --- ### **1. 使用 `mmap` 创建共享内存** 首先创建内存映射文件并写入初始数据: ```python import mmap import os # 创建临时文件并扩展大小 file_path = '/tmp/shm.dat' with open(file_path, 'wb') as f: f.write(b'\x00' * 4096) # 分配 4KB 空间 # 映射到内存 with open(file_path, 'r+b') as f: shm = mmap.mmap(f.fileno(), 0) # 0 表示映射整个文件 ``` --- ### **2. 同步方法 1:文件锁(`fcntl.flock`)** 通过文件锁保护共享内存的访问: ```python import fcntl def process1(): with open(file_path, 'r+b') as f: shm = mmap.mmap(f.fileno(), 0) # 加独占锁(阻塞直到获取锁) fcntl.flock(f, fcntl.LOCK_EX) try: # 读写共享内存 shm.seek(0) data = shm.read(4) new_data = int.from_bytes(data, 'big') + 1 shm.seek(0) shm.write(new_data.to_bytes(4, 'big')) finally: fcntl.flock(f, fcntl.LOCK_UN) # 释放锁 def process2(): # 同上,通过锁同步 pass ``` #### **特点**: - **简单可靠**:直接使用文件锁机制。 - **跨进程兼容**:适用于任意进程(不限于父子进程)。 - **性能较低**:频繁加锁/解锁可能成为瓶颈。 --- ### **3. 同步方法 2:共享内存中的原子标记** 在共享内存中预留特定位置作为锁标志,通过 **原子操作** 实现无锁同步(需依赖底层原子指令): #### **步骤**: 1. 在共享内存头部预留 1 字节作为锁标志(`0` 表示未锁定,`1` 表示锁定)。 2. 使用原子操作(如 `__sync_lock_test_and_set`)实现自旋锁。 ```python from ctypes import CDLL import time libc = CDLL('libc.so.6') class SpinLock: def __init__(self, shm, offset=0): self.shm = shm self.offset = offset # 锁在共享内存中的位置 def lock(self): # 原子操作:若锁为 0,则设置为 1 并返回旧值 while libc.__sync_lock_test_and_set( self.shm, 1, self.offset ) == 1: time.sleep(0.001) # 避免忙等待消耗 CPU def unlock(self): libc.__sync_lock_release(self.shm, self.offset) # 使用示例 def worker(shm): lock = SpinLock(shm, offset=0) lock.lock() try: # 操作共享内存 shm.seek(4) # 跳过锁标志 counter = int.from_bytes(shm.read(4), 'big') counter += 1 shm.seek(4) shm.write(counter.to_bytes(4, 'big')) finally: lock.unlock() ``` #### **特点**: - **高性能**:无锁机制减少上下文切换。 - **复杂性高**:需手动实现原子操作,依赖平台特性(此处仅限 Linux)。 - **风险**:自旋锁可能浪费 CPU 周期,需合理设计等待策略。 --- ### **4. 完整示例(文件锁 + mmap)** ```python import mmap import os import fcntl from multiprocessing import Process def increment_counter(process_id): file_path = '/tmp/shm.dat' with open(file_path, 'r+b') as f: # 映射共享内存 shm = mmap.mmap(f.fileno(), 0) # 加文件锁 fcntl.flock(f, fcntl.LOCK_EX) try: # 读取当前值 shm.seek(0) counter = int.from_bytes(shm.read(4), 'big') # 修改值 counter += 1 print(f"Process {process_id}: New counter = {counter}") # 写回内存 shm.seek(0) shm.write(counter.to_bytes(4, 'big')) finally: fcntl.flock(f, fcntl.LOCK_UN) shm.close() if __name__ == '__main__': # 初始化共享文件 with open('/tmp/shm.dat', 'wb') as f: f.write((0).to_bytes(4, 'big')) # 启动多个进程 processes = [] for i in range(4): p = Process(target=increment_counter, args=(i,)) processes.append(p) p.start() for p in processes: p.join() # 打印最终结果 with open('/tmp/shm.dat', 'rb') as f: print("Final counter:", int.from_bytes(f.read(4), 'big')) ``` --- ### **5. 注意事项** 1. **锁的范围**:文件锁需覆盖所有共享内存操作,避免部分读写未同步。 2. **死锁预防**:确保所有分支路径(如异常)都能释放锁。 3. **性能优化**: - 高频操作优先使用原子标记(需谨慎设计)。 - 低频操作使用文件锁更安全。 4. **资源清理**:进程退出时关闭 `mmap` 对象并删除临时文件。 --- ### **总结** 在 Linux 平台下,`mmap` 共享内存的同步可通过 **文件锁** 或 **原子标记自旋锁** 实现。文件锁简单通用,适合低频场景;原子操作高效但依赖底层实现。根据具体需求选择合适方案!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值