Parallelism

Master Python's concurrent.futures module to build high-performance parallel systems using ThreadPoolExecutor and ProcessPoolExecutor.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

A Future represents a pending operation. It can be in different states:

If a function raises an exception inside a worker, .result() will re-raise it in the main thread:

This is how modern Python backends (FastAPI, aiohttp) achieve massive throughput.

Task queue, worker threads/processes, IPC mechanisms

Pickling costs, lambda limitations, using top-level functions

Grouping tasks for efficiency, reducing pickling overhead

Optimizing executor.map() with chunksize parameter

multiprocessing.Manager, Queue, shared_memory

Pinning threads to CPU cores for consistent latency

AsyncIO → ThreadPool → ProcessPool → AsyncIO pattern

This is TRUE professional pipeline architecture.

Normal multiprocessing copies all data via pickle.

Split work, collect results — backbone of scalable systems

Separate pools for IO, CPU, and orchestration

Parallel map, sequential reduce — Hadoop/Spark ancestor

Task graph, scheduler, future tracking — Ray/Dask concepts

ProcessPool + sockets for multi-machine tasks

AsyncIO orchestration + ThreadPool I/O + ProcessPool CPU

The ultimate architecture combining AsyncIO + Threads + Processes:

You now understand ULTRA-ADVANCED concurrency and parallelism:

This module is the foundation of high-performance Python systems — from ML pipelines to scalable backend services.

📋 Quick Reference — Parallelism

You can now use ThreadPoolExecutor and ProcessPoolExecutor to parallelize real work efficiently and safely.

Up next: Profiling — learn to measure and optimise Python performance like a senior engineer.

Practice quiz

Which concurrent.futures executor is best for I/O-bound work like API calls and downloads?

  • ProcessPoolExecutor
  • asyncio.Executor
  • ThreadPoolExecutor
  • Both are equally bad for I/O

Answer: ThreadPoolExecutor. ThreadPoolExecutor suits I/O-bound tasks where workers spend most time waiting.

Which executor is best for CPU-bound work like image processing or heavy math?

  • ProcessPoolExecutor
  • ThreadPoolExecutor
  • A single thread
  • asyncio alone

Answer: ProcessPoolExecutor. ProcessPoolExecutor uses separate processes, each with its own GIL, to use multiple cores for CPU work.

What does executor.submit(fn, arg) return?

  • The function's result immediately
  • None
  • A list of results
  • A Future object representing the pending work

Answer: A Future object representing the pending work. submit() schedules the call and immediately returns a Future you can query later.

What does calling future.result() do?

  • Cancels the task
  • Blocks until the task finishes, then returns its value (or re-raises its exception)
  • Returns instantly even if not done
  • Starts the task

Answer: Blocks until the task finishes, then returns its value (or re-raises its exception). .result() waits for completion and returns the value, or re-raises any exception from the worker.

What ordering does executor.map(fn, items) guarantee for its results?

  • Results in the same order as the input items
  • Results in completion order (fastest first)
  • Random order
  • Reverse order

Answer: Results in the same order as the input items. map() preserves input order regardless of which task finishes first.

If a function raises an exception inside a worker, when is it surfaced?

  • Immediately in the worker, crashing the program
  • It is silently ignored
  • When you call future.result(), which re-raises it in the calling thread
  • Only when the executor shuts down

Answer: When you call future.result(), which re-raises it in the calling thread. The exception is stored in the Future and re-raised when you call .result().

What is a Future?

  • A scheduled date for the task
  • An object representing work that has started but may not be finished yet
  • A type of thread
  • The result value itself

Answer: An object representing work that has started but may not be finished yet. A Future is a placeholder for a pending result that you can check, wait on, or cancel.

Why can't ThreadPoolExecutor speed up pure-Python CPU-bound loops much?

  • Threads are slower than processes always
  • Threads can't share memory
  • ThreadPoolExecutor has a 1-worker limit
  • The GIL lets only one thread run Python bytecode at a time

Answer: The GIL lets only one thread run Python bytecode at a time. Because of the GIL, threads can't execute Python bytecode truly in parallel, so CPU-bound code stays on one core.

Using a ProcessPoolExecutor as 'with ... as executor:' provides what benefit?

  • It disables the GIL
  • Automatic cleanup/shutdown of workers when the block exits
  • It makes tasks run sequentially
  • It removes the need for results

Answer: Automatic cleanup/shutdown of workers when the block exits. The context manager automatically shuts down the executor, so you don't call shutdown() manually.

Which method returns the stored exception (or None) without raising it?

  • future.result()
  • future.running()
  • future.exception()
  • future.cancel()

Answer: future.exception(). future.exception() returns the exception object if the task failed, or None on success.