Frontend Labs
⋯

37. mmap, shared memory, and pipes

37.1 mmap: a file as memory

import mmap
import os

path = "/tmp/big.bin"
with open(path, "wb") as f:
    f.truncate(64 * 2**20)                    # a 64 MiB sparse file

with open(path, "r+b") as f, mmap.mmap(f.fileno(), 0) as m:
    m[0:5] = b"HELLO"                         # writes go to the page cache directly
    m[-5:] = b"WORLD"
    print(m[:5], m[-5:], len(m))
    print(m.find(b"WORLD"))                   # search without reading the file into Python
os.remove(path)

mmap maps the file into your address space, so pages are loaded on first touch (OS Ch 11) and shared between processes. NumPy's np.memmap, Hugging Face safetensors, and llama.cpp's GGUF loading all use mmap to "load" a multi-gigabyte model in milliseconds: nothing is read until a weight is used, and several processes share one copy in the page cache.

37.2 Shared memory between processes

# labs/shm.py: workers fill one shared NumPy array; nothing is pickled or copied
import numpy as np
from multiprocessing import Process, shared_memory

def worker(name: str, shape: tuple[int, ...], row: int) -> None:
    shm = shared_memory.SharedMemory(name=name)
    arr = np.ndarray(shape, dtype=np.float64, buffer=shm.buf)
    arr[row, :] = row * 10.0                       # writes directly into shared pages
    del arr                                        # drop the view before closing
    shm.close()

if __name__ == "__main__":
    shape = (4, 1_000_000)                         # 32 MB
    shm = shared_memory.SharedMemory(create=True, size=int(np.prod(shape)) * 8)
    try:
        arr = np.ndarray(shape, dtype=np.float64, buffer=shm.buf)
        ps = [Process(target=worker, args=(shm.name, shape, r)) for r in range(shape[0])]
        for p in ps: p.start()
        for p in ps: p.join()
        print(arr[:, 0], arr.sum())
        del arr
    finally:
        shm.close()
        shm.unlink()                               # remove /dev/shm/<name>

This is the fix for the "processes copy my data" problem from Ch 28, and it is what PyTorch's DataLoader uses to hand batches back from worker processes (/dev/shm, which is why Docker's default 64 MB /dev/shm breaks it: run with --shm-size=2g or --ipc=host).

37.3 Pipes

subprocess.PIPE, os.pipe(), and multiprocessing.Pipe are all the kernel pipe from OS Ch 19. A classic deadlock: reading a child's stdout to the end while it blocks writing to a full stderr pipe that you never read. Popen.communicate() reads both concurrently; use it (or run(capture_output=True)) instead of reading the pipes one after the other.


Generated from real-life-python-guide.md · 8 Oct 2026 16:36