Billiard: Improved Python Multiprocessing
billiard is a robust fork of the Python 2.7 `multiprocessing` package, actively maintained by the Celery project. It provides numerous improvements and bugfixes over the standard library's `multiprocessing` module, offering enhanced process-based parallelism for Python applications. It aims to address specific challenges and performance bottlenecks, particularly in distributed task queue systems like Celery, where it serves as a core dependency. The library is under active development with a consistent release cadence.
Warnings
- breaking Python 3.7 or newer is now required. Older Python versions (e.g., Python 3.6 and earlier) are no longer supported since `billiard` v4.0.0.
- breaking On macOS, `billiard` (like `multiprocessing` in Python 3.8+) defaults to the 'spawn' start method instead of 'fork' for safety. Relying on the 'fork' method, particularly in multi-threaded contexts, can lead to crashes.
- gotcha The `if __name__ == '__main__':` block is critical for any code using `billiard` processes. Without it, especially on Windows or when using 'spawn' or 'forkserver' start methods, child processes will re-import the main module and can lead to infinite recursion and process spawning.
- gotcha Avoid using the standard `multiprocessing` module within Celery tasks that are managed by `billiard` (Celery's default). This can lead to nested process creation, which may cause unexpected behavior and issues.
- deprecated `SIGUSR2` was removed from `TERMSIGS_DEFAULT`, which defines signals that terminate processes. This change might affect custom signal handling logic.
Install
-
pip install billiard
Imports
- Process
from billiard import Process
- Queue
from billiard import Queue
- Pool
from billiard import Pool
Quickstart
import os
from billiard import Pool
def worker_function(x):
"""A simple function to be executed by pool workers."""
print(f"Worker PID {os.getpid()}: Processing {x}")
return x * x
if __name__ == '__main__':
# It's crucial to use the if __name__ == '__main__': guard
# especially on Windows or when using 'spawn' start methods.
print(f"Main process PID: {os.getpid()}")
with Pool(processes=3) as pool:
# Map the worker_function to a list of inputs
results = pool.map(worker_function, range(5))
print(f"Results: {results}")
# Alternatively, you can explicitly close and join the pool
# pool = Pool(processes=2)
# async_result = pool.apply_async(worker_function, (10,))
# print(f"Async result: {async_result.get()}")
# pool.close()
# pool.join()