PythonThreadPoolExecutor: map order, as_completed order, errors, defaults and shutdown (OS threads, not in the browser)
import os
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed, wait, FIRST_COMPLETED
def fetch(name, seconds):
time.sleep(seconds)
return f"{name} ({threading.current_thread().name.split('_')[0]})"
jobs = [("slow", 0.3), ("fast", 0.1), ("medium", 0.2)]
with ThreadPoolExecutor(max_workers=3, thread_name_prefix="io") as pool:
start = time.perf_counter()
print("map: ", list(pool.map(fetch, *zip(*jobs))))
print("ran in parallel:", time.perf_counter() - start < 0.5)
futures = {pool.submit(fetch, n, s): n for n, s in jobs}
print("as_completed:", [futures[f] for f in as_completed(futures)])
futs = [pool.submit(fetch, n, s) for n, s in jobs]
done, not_done = wait(futs, return_when=FIRST_COMPLETED)
print("first done:", [f.result() for f in done], "still running:", len(not_done))
def parse(text):
return int(text)
with ThreadPoolExecutor(2) as pool:
fut = pool.submit(parse, "x")
print("exception stored:", type(fut.exception()).__name__)
try:
fut.result()
except ValueError as e:
print("result() raised:", e)
count = getattr(os, "process_cpu_count", os.cpu_count)() or 1
print("default max_workers formula:", min(32, count + 4) == ThreadPoolExecutor()._max_workers)
pool = ThreadPoolExecutor(1)
f1 = pool.submit(time.sleep, 0.2)
f2 = pool.submit(time.sleep, 0.2)
pool.shutdown(wait=True, cancel_futures=False)
print("after shutdown(wait=True):", f1.done(), f2.done())
pool = ThreadPoolExecutor(1)
f1 = pool.submit(time.sleep, 0.2)
f2 = pool.submit(time.sleep, 0.2)
time.sleep(0.05)
pool.shutdown(wait=True, cancel_futures=True)
print("cancel_futures=True:", f1.cancelled(), f2.cancelled())
try:
pool.submit(print, "too late")
except RuntimeError as e:
print("submit after shutdown:", e)
PythonProcessPoolExecutor: parallel CPU work, pickling, isolated globals, max_tasks_per_child (OS processes, not in the browser)
import math
import os
from concurrent.futures import ProcessPoolExecutor
def is_prime(n):
if n < 2:
return False
for d in range(2, math.isqrt(n) + 1):
if n % d == 0:
return False
return True
def count_primes(bounds):
lo, hi = bounds
return sum(1 for n in range(lo, hi) if is_prime(n))
def worker_pid(_):
return os.getpid()
COUNTER = 0
def bump(_):
global COUNTER
COUNTER += 1
return COUNTER
def fails(x):
raise ValueError(f"bad input {x}")
if __name__ == "__main__":
chunks = [(i, i + 50_000) for i in range(0, 200_000, 50_000)]
with ProcessPoolExecutor(max_workers=4) as pool:
print("primes per chunk:", list(pool.map(count_primes, chunks)))
print("total below 200000:", sum(pool.map(count_primes, chunks)))
pids = set(pool.map(worker_pid, range(8)))
print("ran outside the parent:", os.getpid() not in pids)
print("chunksize=100:", sum(pool.map(is_prime, range(1000), chunksize=100)))
try:
pool.submit(fails, 7).result()
except ValueError as e:
print("worker error:", e)
try:
pool.submit(lambda x: x * 2, 21).result()
except Exception as e:
print("lambda:", type(e).__name__)
list(pool.map(bump, range(8)))
print("parent COUNTER after 8 bumps in workers:", COUNTER)
with ProcessPoolExecutor(max_workers=1, max_tasks_per_child=1) as pool:
a = pool.submit(worker_pid, None).result()
b = pool.submit(worker_pid, None).result()
print("max_tasks_per_child=1 gave a fresh process:", a != b)