提示:本网站现在为维护调试状态

Python Concurrency: asyncio vs ThreadPool vs ProcessPool

Choyeon
Choyeon
Sep 5, 2026 1 min read 666 Views
365 words 2,249 chars Updated Nov 8, 2026
Python Concurrency: asyncio vs ThreadPool vs ProcessPool
Table of contents

The GIL makes Python concurrency confusing. IO-bound and CPU-bound workloads demand different models - pick wrong and performance worsens.

Three Models and Code

ThreadPool submits blocking calls; GIL releases on IO waits (10-50 workers). ProcessPool uses subprocesses to evade GIL for CPU work (serialization overhead). asyncio schedules coroutines on one thread for massive light IO concurrency.

import asyncio, time, requests, httpx
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
from typing import List

URLS = [f"https://jsonplaceholder.typicode.com/posts/{i}" for i in range(1, 51)]

def fetch_sync(url: str) -> dict:
    return requests.get(url, timeout=10).json()

async def fetch_async(client, url: str) -> dict:
    r = await client.get(url, timeout=10.0)
    return r.json()

def run_threadpool(urls, maxw=20):
    t0 = time.perf_counter()
    with ThreadPoolExecutor(max_workers=maxw) as ex:
        fs = [ex.submit(fetch_sync, u) for u in urls]
        results = [f.result() for f in as_completed(fs)]
    print(f"[ThreadPool] {len(results)} tasks, {time.perf_counter()-t0:.2f}s")

def cpu_heavy(n):
    sieve = [True]*(n+1); sieve[0]=sieve[1]=False
    for i in range(2, int(n**0.5)+1):
        if sieve[i]: sieve[i*i::i] = [False]*len(sieve[i*i::i])
    return sum(sieve)

def run_processpool(args):
    t0 = time.perf_counter()
    with ProcessPoolExecutor() as ex:
        results = list(ex.map(cpu_heavy, args))
    print(f"[ProcessPool] sum={sum(results)}, {time.perf_counter()-t0:.2f}s")

async def run_asyncio(urls):
    t0 = time.perf_counter()
    async with httpx.AsyncClient() as client:
        tasks = [fetch_async(client, u) for u in urls]
        results = await asyncio.gather(*tasks)
    print(f"[asyncio] {len(results)} tasks, {time.perf_counter()-t0:.2f}s")

if __name__ == "__main__":
    run_threadpool(URLS)
    run_processpool([100000]*8)
    asyncio.run(run_asyncio(URLS))

Decision Framework and Benchmarks

Pure CPU math → ProcessPool. Network/file IO → asyncio or ThreadPool. Mixed → asyncio.run_in_executor offloads CPU to pool while IO stays in coroutines.

Dimension ThreadPool ProcessPool asyncio
Principle Multi-thread shared GIL Multi-process, own GIL Single-thread loop + coroutines
GIL impact Released on IO / useless on CPU No GIL at all No contention
50 HTTP reqs ~5-8s ~40s (fork slow) ~1.5-3s
8 CPU primes ~45s (serial) ~7s (multi-core) ~40s (single)
Max concurrency Hundreds Cores × 2 Thousands+
Migration cost Low Medium (pickle) High (full async chain)

Best Practices

Prefer asyncio for IO first; fall to ThreadPool if dependencies synchronous. Use ProcessPool for CPU tasks and cap workers at 1-2 × core count.

X微博Telegram

About the author

Poll

你最希望 Rosetta 优先加入哪项功能?

社区投票(演示数据,可后台删除)

Comments (7)

0 / 3000
技术笔记控
技术笔记控tech-notes.funSep 3, 2026

受益匪浅,感谢大佬分享!

摸鱼达人小王
摸鱼达人小王Sep 2, 2026

请问一下,文中提到的连接池配置,如果是在高并发场景下,一般设置多少比较合适?

嵌入式极客
嵌入式极客embedded-lab.ioSep 2, 2026

代码示例里那个 SQL 查询是不是应该加个 LIMIT?不然数据量大的时候全表扫描会很慢哦。

产品汪本汪
产品汪本汪pmdiary.ioSep 2, 2026

有个小问题:你文中用的是 @Async 注解,这个默认用的是 SimpleAsyncTaskExecutor,生产环境是不是最好手动配一个线程池?

游戏策划阿哲
游戏策划阿哲Sep 1, 2026

楼主,我按照你的步骤操作后启动报错:Error creating bean with name 'dataSource',版本是 Spring Boot 3.2,请问有遇到过吗?

奶茶味程序媛
奶茶味程序媛Sep 1, 2026

建议加一下关于幂等性的讨论,现在的分布式系统里这个真的太重要了,很多坑都是没处理好幂等导致的。

DevOps小能手
DevOps小能手devops-lab.cnSep 1, 2026

作为一个刚入行的后端新人,这篇文章帮我理清了好多概念,谢谢博主持续输出高质量内容!

Related posts

Dynamic Programming: 5 Universal Templates

Dynamic Programming: 5 Universal Templates

Master DP: 5-step universal framework then 5 high-frequency templates: Knapsack, Subsequence, Interval DP, Bitmask DP, Tree DP, each with canonical code and variant keys.

1 min read
Quick Start with FastAPI - Building High-Performance REST APIs

Quick Start with FastAPI - Building High-Performance REST APIs

FastAPI stands out among modern Python web frameworks with auto-generated docs based on type hints, async support, and Node.js-level performance. This hands-on guide builds a complete CRUD API from scratch covering route definition, request validation, dependency injection, and database integration—master FastAPI essentials in 30 minutes.

This post requires a password 4 min read
RAG in Practice: Embedding + Vector DB + ReRank

RAG in Practice: Embedding + Vector DB + ReRank

Build production-grade RAG from scratch: doc chunking (size + overlap windows), embedding model selection, vector DB retrieval filters, Reranker refining Top-K, plus citation grounding and prompt compression. Complete runnable code included.

1 min read
Alembic Deep Dive: Migrations, Autogenerate, and Branch Merging

Alembic Deep Dive: Migrations, Autogenerate, and Branch Merging

Comprehensive Alembic (official SQLAlchemy migration tool) guide: env.py setup, autogenerate detecting model changes (fields, indexes, constraints), batch scripts, downgrade rollback strategies, plus standard SOP for multi-developer migration branch conflicts and divergent head merging, with production deploy checklist.

1 min read
Advanced Nuxt 3 - Deep Dive into SSR and SSG

Advanced Nuxt 3 - Deep Dive into SSR and SSG

Nuxt 3 is built on Vue 3 and Vite, introducing the new Nitro rendering engine. This article deeply analyzes the working principles, use cases, and performance differences of three core modes: Server-Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR). With code demos, learn to configure rendering strategies, optimize first-screen load time, and implement caching policies.

1 min read
Redis Cache Design Patterns

Redis Cache Design Patterns

Three core caching patterns: Cache-Aside consistency, Write-Through atomic updates, Write-Behind async high-throughput writes, plus solutions for stampede, penetration, and avalanche.

1 min read