Python中mmap对象的多重访问

3

我有许多文件,被映射到内存(作为mmap对象)。在处理过程中,每个文件必须被打开多次。如果只有一个线程,这样做是可以的。但是,当我尝试并行运行任务时,出现了问题:不同的线程无法同时访问同一文件。以下示例说明了问题:

import mmap, threading

class MmapReading(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        for i in range(10000):
            content = mmap_object.read().decode('utf-8')
            mmap_object.seek(0)
            if not content:
                print('Error while reading mmap object')

with open('my_dummy_file.txt', 'w') as f:
    f.write('Hello world')
with open('my_dummy_file.txt', 'r') as f:
    mmap_object = mmap.mmap(f.fileno(), 0, prot = mmap.PROT_READ)

threads = []
for i in range(64):
    threads.append(MmapReading())
    threads[i].daemon = True
    threads[i].start()
for thread in threading.enumerate():
    if thread != threading.current_thread():
        thread.join()

print('Mmap reading testing done!')

每次运行这个脚本时,我都会收到大约20个错误消息。 除了制作每个文件的64个副本(在我的情况下会消耗太多内存),是否有其他解决问题的方法?
3个回答

2

seek(0) 不总是在另一个线程执行 read() 前被执行。

  1. 假设线程1执行了一次读操作,一直读到文件末尾;此时还未执行 seek(0) 操作。
  2. 然后线程2执行了一次读操作。mmap中的文件指针仍然在文件末尾。因此,read() 返回 ''
  3. 由于 content 为空,错误检测代码被触发。

你可以使用切片代替 read() 来实现相同的结果。

    content = mmap_object.read().decode('utf-8')
    mmap_object.seek(0)

使用

    content = mmap_object[:].decode('utf8')

content = mmap_object[:mmap_object.size()] 也可以正常工作。

锁定是另一种方式,但在这种情况下是不必要的。如果您想尝试它,可以使用全局threading.Lock对象,并在实例化MmapReading时将其传递给它。将锁对象存储在实例变量self.lock中。然后在读取/查找之前调用self.lock.acquire(),并在之后调用self.lock.release()。这样做会导致明显的性能损失。

from threading import Lock

class MmapReading(threading.Thread):
    def __init__(self, lock):
        self.lock = lock
        threading.Thread.__init__(self)

    def run(self): 
        for i in range(10000):
            self.lock.acquire()
            mmap_object.seek(0)
            content = mmap_object.read().decode('utf-8')
            self.lock.release()
            if not content:
                print('Error while reading mmap object')

lock = Lock()
for i in range(64):
    threads.append(MmapReading(lock))
.
.
.

请注意,我已更改了读取和查找的顺序;首先进行查找更有意义,将文件指针定位在文件开头。

我理解发生了什么。问题是:如何避免它? - Roman
1
@mhawke,为什么切片操作能够提高性能?我进行了几次快速的谷歌搜索,却几乎没有找到任何有用的信息。 - rite2hhh

1
我看不出你为什么需要使用mmap。mmap是一种在进程之间共享数据的技术。为什么不将内容读入内存(一次!)例如作为列表?然后,每个线程将使用自己的迭代器访问该列表。此外,请注意Python中的GIL,它防止使用多线程进行任何加速。如果您想要这样做,请使用多处理(然后一个mmap文件有意义,但实际上是在各个进程之间共享的)。

mmap 还具有一些其他优点,除了在进程之间共享之外,例如可以高效地随机访问大文件,在此过程中操作系统会进行页面管理。 - mhawke
感谢提到GIL。然而,由于我的实际线程内部存在外部应用程序调用,我确实获得了所需的加速。 - Roman
@mhawke 哦,有趣。 - deets

1
问题在于单个mmap_object被线程共享,因此当A线程调用read并在到达seek之前,B线程也调用read,因此没有获取数据。
您真正需要的是能够复制Python mmap对象而不复制底层mmap的能力,但我看不出有什么方法可以做到这一点。
我认为除了重写对象实现之外,唯一可行的解决方案是对每个mmap对象使用锁(互斥锁等),以防止两个线程同时访问同一对象。

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