Process backend¶
Use the process backend for CPU-heavy Python work on Python 3.11+ when you want work to run across CPU cores.
Good uses¶
parsing large payloads;
scoring algorithms;
CPU-heavy transformations;
compression or hashing;
pure functions over serializable data.
Pickling rules¶
ProcessPoolExecutor sends functions, arguments, and return values between
processes. They must be picklable.
Good:
def calculate_score(payload: dict[str, float]) -> float:
return float(payload["value"]) * 1.5
Avoid:
lambda value: value * 2
Avoid sending:
Redis clients;
database connections;
sockets;
locks;
async functions;
objects with complex process-local state.
Basic example¶
"""
CPU-heavy work with ProcessPoolExecutor backend on Python 3.11.
Use the process backend for CPU-bound work that should use multiple CPU cores.
Important:
- submitted functions must be top-level importable functions
- arguments and return values must be picklable
- do not submit lambdas, nested functions, open sockets, Redis clients, or DB clients
"""
from __future__ import annotations
import asyncio
from leasepool import ExecutorBackend, LeasedExecutorManager
def count_primes(limit: int) -> int:
count = 0
for number in range(2, limit):
for divisor in range(2, int(number**0.5) + 1):
if number % divisor == 0:
break
else:
count += 1
return count
async def main() -> None:
manager = LeasedExecutorManager(
backend=ExecutorBackend.PROCESS,
max_pools=1,
min_pools=1,
workers_per_pool=4,
)
await manager.start()
try:
async with await manager.acquire(owner="cpu-primes") as lease:
results = await asyncio.gather(
lease.run(count_primes, 20_000),
lease.run(count_primes, 21_000),
lease.run(count_primes, 22_000),
lease.run(count_primes, 23_000),
)
print("Prime counts:", results)
finally:
await manager.stop()
if __name__ == "__main__":
asyncio.run(main())
Keyword arguments¶
ExecutorLease.run() supports keyword arguments. With the process backend,
keyword values must also be picklable.
async with await manager.acquire(owner="score") as lease:
result = await lease.run(calculate_score, payload={"value": 21.0})
Executor kwargs¶
Extra keyword arguments passed to LeasedExecutorManager are forwarded to the
underlying executor constructor.
For process pools this lets you pass options such as initializer,
initargs, mp_context, and max_tasks_per_child:
manager = LeasedExecutorManager(
backend="process",
max_pools=1,
workers_per_pool=4,
initializer=configure_worker,
initargs=("worker-config",),
)
When process log forwarding is enabled, leasepool composes its own initializer with your initializer so both run.
Operational notes¶
Run process-backend examples as files:
python examples/07_process_backend_cpu_work.py
Do not paste process-pool examples into an interactive REPL. Worker processes must be able to import top-level functions from a module.