liveBook · Manning

Thread pool

As explained in Python Concurrency with asyncio

A thread pool executor is an execution service that runs submitted callables using a reusable pool of worker threads.

A thread pool executor runs submitted callables using a reusable group of worker threads. It is especially useful when blocking APIs, such as requests, would otherwise stop an asyncio event loop from doing other work. section 2.7.2 section 7.2.2

Use a thread pool executor to:

  • Distribute blocking I/O tasks across worker threads instead of executing them serially. section 7.2.2
  • Connect blocking functions to asyncio with loop.run_in_executor and await their results with asyncio.gather. section 7.2.3
  • Control concurrency with the pool rather than creating one thread for every task; the default worker count is min(32, os.cpu_count() + 4). section 7.2.2

Running many blocking HTTP requests

A ThreadPoolExecutor can map get_status_code across 1,000 URLs. The threaded version can finish in roughly 8–9 seconds on an eight-core machine with a fast connection, compared with more than 100 seconds synchronously. Setting max_workers to 1,000 is shown as an alternative, but the default pool avoids creating a surprising number of threads. section 7.2.2

Listing 7.5 Using a thread pool executor with asyncio
import functools
import requests
import asyncio
from concurrent.futures import ThreadPoolExecutor
from util import async_timed
 
 
def get_status_code(url: str) -> int:
    response = requests.get(url)
    return response.status_code
 
 
@async_timed()
async def main():
    loop = asyncio.get_running_loop()
    with ThreadPoolExecutor() as pool:
        urls = ['https:// www .example .com' for _ in range(1000)]
        tasks = [loop.run_in_executor(pool, functools.partial(get_status_code, url)) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)
 
asyncio.run(main())

This runnable pattern creates a pool, submits blocking requests through loop.run_in_executor, and gathers their results while other asynchronous code can continue. section 7.2.3

How Python Concurrency with asyncio covers it

How deep it goes
Explains worker pools, sizing, queue submission, mapping, and asyncio integration, while deferring broader thread-safety details.
Examples
Demonstrates 1,000 requests with requests, asyncio gathering, and password hashing with hashlib and scrypt.
What it assumes
Assumes basic Python functions and asyncio, while explaining blocking calls and executor usage progressively.
What you can do after
Apply it via concurrent.futures and asyncio to run blocking functions concurrently and collect their results.
How it connects
Connects ThreadPoolExecutor with concurrent.futures, asyncio, event loops, blocking I/O, ProcessPoolExecutor, and Future results.

Also explained in

Compare how 10 books explain thread pool →

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.