liveBook · Manning

Multithreading

As explained in Python Concurrency with asyncio

Multithreading is a concurrency technique in which multiple threads execute within a process and share its memory.

Multithreading runs multiple threads inside one process. Threads share the process’s memory, so they can perform work concurrently while accessing shared variables directly. section 1.4 section 1.4.2

Listing 1.3 Creating a multithreaded Python application
import threading
 
 
def hello_from_thread():
    print(f'Hello from thread {threading.current_thread()}!')
 
 
hello_thread = threading.Thread(target=hello_from_thread)
hello_thread.start()
 
total_threads = threading.active_count()
thread_name = threading.current_thread().name
 
print(f'Python is currently running {total_threads} thread(s)')
print(f'The current thread is {thread_name}')
 
hello_thread.join()

Creates a thread with threading.Thread, starts it, observes active threads, and waits for completion with join. section 1.4.2

In Python, threads are chiefly useful for blocking I/O because the global interpreter lock prevents one CPython process from executing multiple Python bytecode instructions simultaneously. CPU-bound Python code therefore may not run faster with threads, although libraries such as hashlib and NumPy can release the GIL during low-level work. section 1.5 section 7.5

Use multithreading when the concurrency problem matches the execution model:

  • Run existing blocking I/O libraries, such as requests, in thread pools while retaining asyncio APIs such as gather and wait. section 7.0
  • Use ThreadPoolExecutor or run_in_executor to distribute work and collect results asynchronously. section 6.3.1 section 7.6
  • Protect shared, non-thread-safe data with threading.Lock around critical sections; consider reentrant locks when avoiding deadlocks. section 7.3 section 7.6
  • Consider threads for CPU-intensive NumPy operations only after profiling and benchmarking, because GIL release and performance gains depend on the implementation. section 7.5.2

Concurrent requests with shared progress

A ThreadPoolExecutor can run many requests concurrently while a shared counter records completed requests. Wrap the counter increment in a Lock, and use an asyncio reporter to display progress without corrupting shared state. section 7.3

Threads differ from processes: processes do not share memory by default and can execute Python bytecode concurrently because each process has its own GIL. They also differ from asyncio’s single-threaded event loop, though asyncio can interoperate with both models. section 1.5 section 1.1

How Python Concurrency with asyncio covers it

How deep it goes
Explains thread creation, joining, GIL constraints, shared-memory races, locks, pools, deadlocks, and selected GIL-releasing workloads.
Examples
Demonstrates hello-world threads, concurrent requests with progress tracking, Fibonacci timing, and NumPy row means.
What you can do after
Apply it via threading, ThreadPoolExecutor, asyncio, and Lock to run blocking work concurrently and synchronize shared state.
How it connects
Connects multithreading with processes, multiprocessing, asyncio, event loops, blocking I/O, locks, race conditions, and the global interpreter lock.

Also explained in

Compare how 11 books explain multithreading →

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.