我正在编写一个程序,该程序使用 Python 的multiprocessing
模块来加速 CPU 密集型任务,我希望我创建的子进程访问最初在父进程中创建的内存映射,而无需复制它。根据multiprocessing
文档,从 Python 3.4 开始,子进程默认不再继承文件描述符,因此我尝试使用os.set_inheritable()
来覆盖该行为。
这是我为演示该问题而制作的快速模型:
DATA = r"data.csv"
from sys import platform
WINDOWS = platform.startswith("win")
import os
from multiprocessing import Process
import mmap
from typing import Optional
def child(fd: int, shm_tag: Optional[str]) -> None:
if shm_tag: # i.e. if using Windows
mm = mmap.mmap(fd, 0, shm_tag, mmap.ACCESS_READ)
else:
mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ)
mm.close()
if __name__ == "__main__":
# Some code differs on Windows
WINDOWS = platform.startswith("win")
# Open file
fd = os.open(DATA, os.O_RDONLY | os.O_BINARY if WINDOWS else os.O_RDONLY)
os.set_inheritable(fd, True)
# Create memory map from file descriptor
if WINDOWS:
shm_tag = "shm_mmap"
mm = mmap.mmap(fd, 0, shm_tag, mmap.ACCESS_READ)
else:
shm_tag = None
mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ)
# Run child process
(p := Process(target = child, args = (fd, shm_tag), daemon = True)).start()
p.join()
p.close()
mm.close()
os.close(fd)
这根本不起作用 - 或者至少在我主要测试的 Windows* 上不起作用。我在子进程中收到一个错误,严重暗示文件描述符实际上不是继承的:
Process Process-1:
Traceback (most recent call last):
File "C:Program FilesPython38libmultiprocessingprocess.py", line 315, in _bootstrap
self.run()
File "C:Program FilesPython38libmultiprocessingprocess.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "C:Users[N.D.]Documentstest.py", line 12, in child
mm = mmap.mmap(fd, 0, shm_tag, mmap.ACCESS_READ)
ValueError: cannot mmap an empty file
此外,无论我是通过True
还是False
传递给os.set_inheritable()
,我都会得到完全相同的错误,好像它实际上并没有区别。
这是怎么回事?我是否错误地使用了mmap
模块?
*可能相关:Windows使用spawn()
而不是fork()
来创建新进程,如果您尝试内存映射空文件,则会引发异常。
感谢Eryk Sun的评论,我能够做出一个有效的实现:
DATA = r"data.csv"
from sys import platform
if platform.startswith("win"):
WINDOWS = True
from msvcrt import get_osfhandle
else:
WINDOWS = False
import os
from multiprocessing import Process
import mmap
from typing import Optional
def child(fd_or_size: int, shm_tag: Optional[str]) -> None:
if WINDOWS:
mm = mmap.mmap(-1, fd_or_size, shm_tag, mmap.ACCESS_READ)
else:
mm = mmap.mmap(fd_or_size, 0, mmap.MAP_SHARED, mmap.PROT_READ)
mm.close()
if __name__ == "__main__":
# Open file
fd = os.open(DATA, os.O_RDONLY | os.O_BINARY if WINDOWS else os.O_RDONLY)
# Create memory map from file descriptor
if WINDOWS:
# Obtain underlying file handle from file descriptor
os.set_handle_inheritable(get_osfhandle(fd), True)
shm_tag = f"test_mmap_{os.getpid()}"
mm = mmap.mmap(fd, 0, shm_tag, mmap.ACCESS_READ)
else:
os.set_inheritable(fd, True)
mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ)
# Run child process
(p := Process(target = child, args = (mm.size() if WINDOWS else fd, shm_tag),
daemon = True)).start()
p.join()
p.close()
mm.close()
os.close(fd)
重要更改(全部在 Windows 上(:
- 使用
get_osfhandle()
从文件描述符获取基础文件句柄 - 为内存映射指定特定于进程的标记名称
- 在子进程中,通过提供已知的映射大小来附加到内存映射。