---
title: "Python Joblib in 2026: Processes, Threads, Memmap, and Caching"
description: "A practical 2026 guide to using Joblib for Python processes, threads, memory-mapped arrays, and cached computations without low-level multiprocessing boilerplate."
author: Aleksei Aleinikov
date: 2026-04-28
updated: 2026-06-27
lang: en
tags: [python, joblib, parallelism, multiprocessing, threading, caching, performance]
canonical: https://www.alekseialeinikov.com/en/blog/topics/programming/python-joblib-2026-processes-threads-memmap-caching
source: alekseialeinikov.com
---

# Python Joblib in 2026: Processes, Threads, Memmap, and Caching

At first glance **Python** can look single-threaded by nature. Every attempt to speed it up seems to turn into a fight with the GIL, multiprocessing details, and pages of boilerplate.

**Joblib** keeps that story much calmer.

It can start processes or threads, distribute work, cache results on disk, and help process data that does not fit comfortably into RAM. This 2026 guide shows how to get the most from Joblib without diving into low-level concurrency primitives.

![Python and Joblib in 2026: let the workers handle the heavy lifting while you keep the bigger picture visible.](https://www.alekseialeinikov.com/blog/1_5zCVGXEgQqgBFsEhXdxUQg.webp)

Quick fact: Joblib was born inside the scikit-learn ecosystem to serialize NumPy arrays efficiently and run expensive tasks in parallel. Today it is useful for analysts, ML engineers, data engineers, and backend developers who care about practical performance.

## Why Joblib Instead of multiprocessing?

The standard `multiprocessing` module is powerful, but it asks you to manage a lot of ceremony: pools, argument packing, result collection, process-safe objects, and platform-specific behavior.

Joblib gives you a smaller API:

- `Parallel` decides how work is distributed,
- `delayed` wraps function calls for parallel execution,
- `n_jobs` controls how many workers are used,
- `prefer="threads"` switches to threads for I/O-heavy work,
- `Memory` caches expensive function results on disk.

![Joblib vs multiprocessing: fewer moving parts for common parallel workloads.](https://www.alekseialeinikov.com/blog/1_J2vbt8Ms-HEtQTm8ysW6jg.webp)

## Quick Start: CPU-Bound Work in Multiple Processes

**For CPU-heavy work, processes are usually the right default because they avoid the limitations of the GIL.**

```python
from math import factorial
from joblib import Parallel, delayed


def heavy(x: int) -> int:
    return factorial(x)


numbers = list(range(10_000))
result = Parallel(n_jobs=-1)(delayed(heavy)(n) for n in numbers)

print(result[:5])
```

`n_jobs=-1` uses every available core.

`delayed` wraps your function call so Joblib can scatter the work across worker processes.

On Windows, remember to put multiprocessing code behind the usual guard:

```python
if __name__ == "__main__":
    ...
```

That prevents accidental process-spawning loops.

## Threads for I/O-Bound Tasks

**If your function mostly waits for network, disk, or a remote API, threads can be a better fit than processes.**

```python
import time
import requests
from joblib import Parallel, delayed


URLS = [
    "https://example.com",
    "https://httpbin.org/delay/2",
    "https://python.org",
    "https://www.wikipedia.org",
]


def fetch(url: str) -> tuple[str, float, int]:
    start = time.perf_counter()
    response = requests.get(url, timeout=10)
    duration = time.perf_counter() - start
    return url, duration, len(response.content)


results = Parallel(n_jobs=4, prefer="threads")(
    delayed(fetch)(url) for url in URLS
)

for url, seconds, size in results:
    print(f"{url} -> {size} bytes in {seconds:.2f}s")
```

The GIL is less painful here because the program spends much of its time waiting. A handful of threads can keep several requests moving at once while the main code stays readable.

## Memmap: Processing Gigabytes without Copying Everything

**When multiple workers need access to large NumPy arrays, copying the same data into every process can waste huge amounts of RAM.**

Memory-mapped files help because the data lives on disk and is mapped into virtual memory. Multiple processes can reuse the same pages instead of duplicating the full array.

```python
import os
import tempfile

import numpy as np
from joblib import Parallel, delayed


tmp = tempfile.mkdtemp()
file_path = os.path.join(tmp, "sat_images.mmap")

shape = (10_000, 1024, 1024)
cube = np.random.randint(0, 256, size=shape, dtype=np.uint8)

fp = np.memmap(file_path, dtype="uint8", mode="w+", shape=shape)
fp[:] = cube[:]
del fp
del cube

images = np.memmap(file_path, dtype="uint8", mode="r", shape=shape)


def mean_intensity(image) -> float:
    return float(image.mean())


means = Parallel(n_jobs=8)(
    delayed(mean_intensity)(images[i]) for i in range(images.shape[0])
)

print(f"Computed mean brightness for {len(means)} images.")
```

For a local experiment, reduce `shape` first. The important idea is the pattern: write the large array to a memory-mapped file, reopen it read-only, and let workers process slices without copying the entire dataset.

## Caching Repeated Computations

**Joblib also shines when a function is expensive and deterministic enough to cache.**

```python
import datetime as dt
import requests
from joblib import Memory


memory = Memory("~/.cache/joblib_currency", verbose=0)


@memory.cache
def get_rate(date: str, pair: str = "EURUSD") -> float:
    print(f"Requesting FX rate for {date}")
    response = requests.get(
        f"https://api.exchangerate.host/{date}",
        params={"base": pair[:3], "symbols": pair[3:]},
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()
    return data["rates"][pair[3:]]


today = dt.date.today().isoformat()

print(get_rate(today))
print(get_rate(today))
```

The first call performs the work. The second call can come back from disk cache, which saves time and sometimes saves API costs.

## Handy Extras

Use `Parallel(..., verbose=10)` when you want progress output in the console.

Use `max_nbytes` to control when large objects are written to shared memory instead of being copied.

Use `n_jobs=1` when debugging. Single-worker mode removes multiprocessing noise while keeping the same code shape.

## Conclusion

**In 2026, Joblib remains one of the simplest ways to add multiprocessing, multithreading, memory-mapped data handling, and disk caching to Python projects.**

A few lines can use every CPU core, avoid unnecessary RAM copies, and skip repeated expensive work without forcing you into low-level concurrency code.
