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










