Python中多线程发送HTTP请求的三种方案对比
为什么需要多线程?
单线程发送 100 个请求,每个 0.5 秒,总耗时 50 秒。多线程发送同样 100 个请求,总耗时可能压到 3~5 秒。
差距不是"快一点",是量级 difference。
但多线程不是银弹。用错了,比单线程还慢,还容易把对方服务打挂。
三种主流方案对比
| 方案 | 适用场景 | 上手难度 | 性能上限 |
|---|---|---|---|
threading + Queue | 简单批量任务 | ⭐⭐ | 中等 |
concurrent.futures.ThreadPoolExecutor | 大多数场景首选 | ⭐ | 高 |
asyncio + aiohttp | 高并发、IO密集 | ⭐⭐⭐⭐ | 最高 |
结论先给:80% 的场景用 ThreadPoolExecutor 就够了。
方案一:threading + Queue(手动控制)
适合需要精细控制线程数、任务队列的场景。
import threading
import queue
import requests
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
result_queue = queue.Queue()
def worker(q):
while not q.empty():
url = q.get()
try:
resp = requests.get(url, timeout=5)
result_queue.put((url, resp.status_code))
except Exception as e:
result_queue.put((url, str(e)))
finally:
q.task_done()
# 启动 5 个线程
threads = []
for _ in range(5):
t = threading.Thread(target=worker, args=(queue.Queue(),))
t.start()
threads.append(t)
# 填入任务
task_queue = queue.Queue()
for url in urls:
task_queue.put(url)
# 重新分配任务给 worker(简化写法,实际应把 task_queue 传进去)
for t in threads:
t.join()
while not result_queue.empty():
print(result_queue.get())
问题:代码啰嗦,手动管理线程生命周期,容易写错。
方案二:ThreadPoolExecutor(推荐)
Python 3.2+ 内置,几行代码搞定。
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
def fetch(url):
try:
resp = requests.get(url, timeout=5)
return url, resp.status_code
except Exception as e:
return url, str(e)
t1 = time.time()
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(fetch, url): url for url in urls}
for future in as_completed(futures):
url, result = future.result()
print(f"{url} -> {result}")
print(f"耗时:{time.time() - t1:.2f}s")
优点:
- 自动管理线程池,不用手动
start/join as_completed按完成顺序返回结果,不用等全部完成max_workers控制并发数,防止把对方打挂
方案三:asyncio + aiohttp(高性能)
适合 上千级别并发,或者你本身就在用异步框架(FastAPI、Sanic 等)。
import asyncio
import aiohttp
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
async def fetch(session, url):
try:
async with session.get(url, timeout=5) as resp:
return url, resp.status
except Exception as e:
return url, str(e)
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, result in results:
print(f"{url} -> {result}")
t1 = time.time()
asyncio.run(main())
print(f"耗时:{time.time() - t1:.2f}s")
优点:单线程实现高并发,资源占用极低。
代价:学习曲线陡,所有调用链必须是 async 的,混用会出问题。
常见坑
1. max_workers 不是越大越好
workers = 100 # ❌ 大概率更慢,还可能被封 IP workers = 10 # ✅ 大部分场景够用
经验值:10~20 是 sweet spot。超过 50 基本没收益,还可能触发对方限流。
2. 忘了设 timeout
requests.get(url) # ❌ 对方不响应,你的线程就永远挂着 requests.get(url, timeout=5) # ✅ 5秒没响应就放弃
3. 混用 Session 和多线程
requests.Session() 不是线程安全的。
session = requests.Session() # ❌ 多线程共享同一个 session 会出问题
# 正确做法:每个线程自己创建 session
def fetch(url):
with requests.Session() as s: # ✅
return s.get(url).text
或者用 ThreadPoolExecutor 配合 requests 本身就没问题,因为每个 submit 独立执行函数,函数内自己创建 session。
4. 对方有反爬
多线程 = 高频率访问 = 容易触发风控。
应对:
- 加随机延迟:
time.sleep(random.uniform(0.5, 2)) - 换 User-Agent 池
- 用代理 IP 池
选型决策树
请求量 < 100?
→ 单线程 + requests 就够了,别过度设计
请求量 100~1000?
→ ThreadPoolExecutor(方案二)
请求量 > 1000 或已在用异步框架?
→ asyncio + aiohttp(方案三)
需要精细控制任务优先级/重试/失败队列?
→ threading + Queue(方案一)或 Celery
一句话总结
多线程发送请求的核心不是"开更多线程",而是控制并发数 + 复用连接 + 设置超时。
ThreadPoolExecutor 解决了 80% 的问题,剩下 20% 才需要上 asyncio 或 Celery。
到此这篇关于Python中多线程发送HTTP请求的三种方案对比的文章就介绍到这了,更多相关Python多线程发送HTTP请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Python tensorflow实现mnist手写数字识别示例【非卷积与卷积实现】
这篇文章主要介绍了Python tensorflow实现mnist手写数字识别,结合实例形式分析了基于tensorflow模块使用非卷积与卷积算法实现手写数字识别的具体操作技巧,需要的朋友可以参考下2019-12-12
opencv python 图像轮廓/检测轮廓/绘制轮廓的方法
这篇文章主要介绍了opencv python 图像轮廓/检测轮廓/绘制轮廓的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2019-07-07


最新评论