A thread pool is like a small crew already waiting for jobs. Instead of creating a new thread whenever work appears, a program starts a collection of long-running worker threads, gives them submitted tasks, and reuses them when each task finishes. The main thread can hand work to the pool rather than managing every worker itself. section 5.4 section 5.6
This arrangement avoids the cost of repeatedly creating threads and keeps a task failure, such as an exception, from ending the worker thread. It is especially useful when individual tasks take less time than creating a new thread would take. Pool management still has trade-offs: choosing the right number of workers depends on workload, system load, and hardware, and pools may use fixed or dynamically changing sizes. section 5.4
# Chapter 5/thread_pool.py
import time
import queue
import typing as T
from threading import Thread, current_thread
Callback = T.Callable[..., None]
Task = T.Tuple[Callback, T.Any, T.Any]
TaskQueue = queue.Queue
class Worker(Thread):
def __init__(self, tasks: queue.Queue[Task]):
super().__init__()
self.tasks = tasks
def run(self) -> None:
while True: #1
func, args, kargs = self.tasks.get() #1
try: #1
func(*args, **kargs) #1
except Exception as e: #1
print(e) #1
self.tasks.task_done() #1
class ThreadPool:
def __init__(self, num_threads: int):
self.tasks: TaskQueue = queue.Queue(num_threads) #2
self.num_threads = num_threads
for _ in range(self.num_threads): #3
worker = Worker(self.tasks) #3
worker.setDaemon(True) #3
worker.start() #3
def submit(self, func: Callback, *args, **kargs) -> None:
self.tasks.put((func, args, kargs))
def wait_completion(self) -> None:
self.tasks.join() #4
def cpu_waster(i: int) -> None:
name = current_thread().getName()
print(f"{name} doing {i} work")
time.sleep(3)
def main() -> None:
pool = ThreadPool(num_threads=5) #5
for i in range(20): #6
pool.submit(cpu_waster, i) #6
print("All work requests sent")
pool.wait_completion()
print("All work completed")
if __name__ == "__main__":
main()
A compact Python implementation shows the basic loop: workers take functions from a queue, run them, handle exceptions, and mark tasks complete before accepting more work. section 5.4
Splitting a large search
To check many possible passwords, divide the search range into chunks and assign chunks to workers. Multiple chunks can run concurrently; an implementation creates workers based on available CPU cores and reports more than a threefold speedup over the earlier sequential version. section 5.5
A pool can also support asynchronous communication. An event loop can send a CPU-heavy job to a thread pool, immediately return a Future, and later receive notification that the result is ready. This keeps the caller’s execution thread free to continue other work while the job runs. section 12.10









