为何Python3中asyncio代码运行速度慢于threading代码?
为什么我的asyncio请求代码比threading慢?
我近期研究Python3的asyncio并做测试,发现它的运行速度比基于threading的代码慢很多,原以为两者耗时应该相近,推测是自己对asyncio工作原理有认知偏差,希望得到相关见解。
我的asyncio测试代码
import asyncio import requests async def main(): for i in range(10): task = asyncio.create_task(foo('https://catfact.ninja/fact')) async def foo(api_url): x = requests.get(api_url) print(x.json().get('fact')) asyncio.run(main())
运行命令及结果
执行time python3.11 asyncio_test.py,输出及耗时如下:
$ time python3.11 asyncio_test.py The Maine Coon cat is America's only natural breed of domestic feline. It is 4 to 5 times larger than the Singapura, the smallest breed of cat. One reason that kittens sleep so much is because a growth hormone is released only during sleep. The smallest pedigreed cat is a Singapura, which can weigh just 4 lbs (1.8 kg), or about five large cans of cat food. The largest pedigreed cats are Maine Coon cats, which can weigh 25 lbs (11.3 kg), or nearly twice as much as an average cat weighs. A cat named Dusty, aged 17, living in Bonham, Texas, USA, gave birth to her 420th kitten on June 23, 1952. Phoenician cargo ships are thought to have brought the first domesticated cats to Europe in about 900 BC. A kitten will typically weigh about 3 ounces at birth. The typical male housecat will weigh between 7 and 9 pounds, slightly less for female housecats. The average litter of kittens is between 2 - 6 kittens. In Siam, the cat was so revered that one rode in a chariot at the head of a parade celebrating the new king. The name "jaguar" comes from a Native American word meaning "he who kills with one leap". The normal body temperature of a cat is between 100.5 ° and 102.5 °F. A cat is sick if its temperature goes below 100 ° or above 103 °F. real 0m9.308s user 0m0.551s sys 0m0.039s
我的threading测试代码
import queue import threading import requests def foo(q): x = requests.get('https://catfact.ninja/fact') q.put(x.json().get('fact')) q = queue.Queue() for i in range(10): t = threading.Thread(target=foo, args=(q,)) t.start() for i in range(10): print(q.get())
运行命令及结果
执行time python3.11 requests_cats.py,输出及耗时如下:
$time python3.11 requests_cats.py Cat's urine glows under a black light. Cats lose almost as much fluid in the saliva while grooming themselves as they do through urination. Cats can be taught to walk on a leash, but a lot of time and patience is required to teach them. The younger the cat is, the easier it will be for them to learn. A queen (female cat) can begin mating when she is between 5 and 9 months old. In 1987 cats overtook dogs as the number one pet in America. A cat's nose is as unique as a human's fingerprint. Cat bites are more likely to become infected than dog bites. People who are allergic to cats are actually allergic to cat saliva or to cat dander. If the resident cat is bathed regularly the allergic people tolerate it better. Cats respond most readily to names that end in an "ee" sound. The largest cat breed is the Ragdoll. Male Ragdolls weigh between 12 and 20 lbs (5.4-9.0 k). Females weigh between 10 and 15 lbs (4.5-6.8 k). real 0m2.098s user 0m1.239s sys 0m0.047s
问题根源与解决方案
核心问题:你的asyncio代码未实现真正的异步IO
你在asyncio协程里使用了requests.get(),这是一个同步阻塞的HTTP请求库。当调用它时,整个asyncio事件循环会被阻塞,直到请求完成。这导致10个请求只能串行执行,总耗时约为单个请求的10倍,自然远慢于threading版本的并行执行。
而threading版本中,每个线程独立发起同步请求,操作系统会调度多个线程并行处理,所以总耗时接近单个请求的时间。
正确的asyncio实现方式
要让asyncio发挥异步优势,必须使用异步HTTP库(比如aiohttp),这类库的IO操作是非阻塞的,事件循环可以在等待请求响应时切换到其他协程执行。
修正后的代码示例:
import asyncio import aiohttp async def main(): # 创建异步HTTP会话 async with aiohttp.ClientSession() as session: tasks = [] for i in range(10): # 创建异步任务并加入列表 task = asyncio.create_task(foo(session, 'https://catfact.ninja/fact')) tasks.append(task) # 等待所有任务完成 await asyncio.gather(*tasks) async def foo(session, api_url): # 发起异步GET请求 async with session.get(api_url) as response: # 异步解析响应JSON data = await response.json() print(data.get('fact')) asyncio.run(main())
关键修正点说明
- 使用异步HTTP客户端:
aiohttp.ClientSession提供非阻塞的请求能力,不会阻塞事件循环。 - 等待所有任务完成:原代码中没有等待
create_task创建的任务执行,虽然事件循环退出前可能会等待,但即使如此,每个请求仍是串行的;asyncio.gather()会并发调度所有任务。 - 异步IO操作:所有涉及IO的步骤(请求、JSON解析)都使用异步版本并加
await,让事件循环可以在等待时切换协程。
补充说明
asyncio的优势在高并发场景(比如数百上千个请求)中会更明显:线程的内存开销(每个线程约几MB)和上下文切换成本会随线程数增加而飙升,而asyncio用单线程事件循环能更高效处理大量并发任务。在低并发(比如10个请求)下,正确实现的asyncio和threading性能差距不大,甚至threading可能因实现简单看起来更快。
内容的提问来源于stack exchange,提问作者user14752375
相关产品推荐
相关产品推荐

