liveBook · Manning

Multiprocessing

As explained in Python Concurrency with asyncio

Multiprocessing is a concurrency technique in which multiple processes perform work concurrently, typically with a parent process managing child processes.

Multiprocessing runs work concurrently across multiple processes. A parent process creates and manages child processes, then distributes work to them. Unlike threads, separate processes can execute Python bytecode concurrently because each process has its own global interpreter lock. section 1.4.2 section 1.5

Use multiprocessing primarily for CPU-intensive work. Marking CPU-bound functions async does not make them run concurrently: the event loop remains blocked unless the work is moved into a process pool. section 1.4.2 section 2.7.1

A practical multiprocessing workflow follows these ideas:

  • Create a process with a target function, call start to begin it, and call join to wait for completion. section 1.4.2
  • Use ProcessPoolExecutor when work can be divided among independent inputs; submit or map distribute calls and provide results as they complete. section 6.3.1
  • Serialize results sent back to the parent process, and protect shared state with locks when multiple processes may access it simultaneously. section 6.6 section 6.7

Running a child process

A minimal program defines hello_from_process, creates multiprocessing.Process(target=hello_from_process), starts the child, prints from the parent, and joins the child. The separate process IDs demonstrate that the functions run in different processes. section 1.4.2

Scaling CPU-bound counting

A ProcessPoolExecutor can apply count to numbers such as 1, 3, 5, 22, and 100000000. Small counts finish quickly, while the large count takes longer, illustrating independent work distributed across pooled processes. section 6.3.1

Listing 6.4 Process pool executors
import time
from concurrent.futures import ProcessPoolExecutor
 
 
def count(count_to: int) -> int:
    start = time.time()
    counter = 0
    while counter < count_to:
        counter = counter + 1
    end = time.time()
    print(f'Finished counting to {count_to} in {end - start}')
    return counter
 
 
if __name__ == "__main__":
    with ProcessPoolExecutor() as process_pool:
        numbers = [1, 3, 5, 22, 100000000]
        for result in process_pool.map(count, numbers):
            print(result)

Inspect the complete ProcessPoolExecutor example to see pool creation, mapped inputs, timing output, and the main-process guard. section 6.3.1

How Python Concurrency with asyncio covers it

How deep it goes
Explains parent-child processes, start and join, process pools, serialization, shared state, and event-loop-per-process designs.
Examples
Demonstrates child-process greetings, Fibonacci computation, pooled counting, and database queries handled by one event loop per process.
What it assumes
Assumes basic distinctions between processes, threads, concurrency, and CPU-bound versus I/O-bound work.
What you can do after
Builds process-based workers, distribute CPU-bound calls through ProcessPoolExecutor, and combine process pools with asyncio.
How it connects
Connects multiprocessing with concurrency, multithreading, the global interpreter lock, asyncio, event loops, process pools, locks, and race conditions.

Also explained in

Compare how 3 books explain multiprocessing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Python Concurrency with asyncio. The section links point to the parts of the book it draws on.