服务端
现在有一个api接口 http://127.0.0.1:18081/hello 批量请求该接口,该接口中有一个5s的阻塞。使用循环,多进程,多线程,协程等四种方式,一共请求10次,比较总的请求耗时。
import time
from flask import Flask
app = Flask(__name__)
@app.route('/hello')
def hello_world():
time.sleep(5)
return "hello world"
if __name__ == '__main__':
app.run(port=8090, host="0.0.0.0")
四种请求方法
请求函数
请求接口使用最常见好用的http请求包requests,三种请求方法使用同一个函数。
函数如下:
def blocking_way():
res = requests.get("http://172.16.9.124:8090/hello")
return res.content
循环
循环调用请求函数10次
# 同步
def sync_way():
res = []
for i in range(10):
res.append(blocking_way())
return len(res)
start = time.time()
res = sync_way()
print(res)
end = time.time()
print("**********sync************")
print(end-start)
结果:
50.0388023853302
多进程
开启10个进程并发请求函数
# 多进程
def process_way():
workers = 10
with futures.ProcessPoolExecutor(workers) as executor:
futs = {executor.submit(blocking_way) for i in range(10)}
return len([fut.result() for fut in futs])
start = time.time()
res = process_way()
end = time.time()
print("**************process***********")
print(end-start)
结果:
5.066945791244507
多线程
开启10个线程并发请求函数
# 多线程
def thread_way():
worker = 10
with futures.ThreadPoolExecutor(worker) as executor:
futs = {executor.submit(blocking_way) for i in range(10)}
return len([fut.result() for fut in futs])
start = time.time()
res = thread_way()
end = time.time()
print("**************threading***********")
print(end-start)
结果:
5.034665822982788
协程
开启10个协程
import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession(loop=loop) as session:
async with session.get(url) as response:
response = await response.read()
return response
if __name__ == "__main__":
import time
start = time.time()
url = "http://127.0.0.1:8090/hello"
loop = asyncio.get_event_loop()
tasks = [fetch(url) for i in range(10)]
res = loop.run_until_complete(asyncio.gather(*tasks))
end = time.time()
print(end-start)
结果:
5.018295049667358
耗时比较
并发类型 | 耗时 单位秒 |
---|---|
循环 | 50.0388023853302 |
多进程 | 5.066945791244507 |
多线程 | 5.034665822982788 |
协程 | 5.018295049667358 |
分析
同步
每一次请求会阻塞5s,因为10个请求是按照顺序执行,所有一共阻塞50s左右
多进程
开启10个进程,每一个进程完成一次请求,请求之间是互相隔离的,10个请求不存在阻塞。理论上来说10个请求相当于1个请求,所以也就相当于1次请求的时间5s左右
多线程
多线程是一个进程中的并发,也就是说10次请求是在一个进程中完成的。由于GIL锁的存在,一个Python进程中,只允许有一个线程处于运行状态。
为什么线程结果还是如预期,耗时缩减到了十分之一?
因为python线程的调度机制。python遇到阻塞时当前线程会释放GIL,让别的线程有执行机会。所以一个线程执行到 requests.get 时让出GIL,下一个线程执行,这个过程就不存在阻塞。
当第一个让出GIL锁的线程下一次被调度到就有可能已经完成接口请求,下面就是执行剩下的逻辑。整个执行过程主要是阻塞的时间,业务逻辑耗时非常少,所以从10个请求整体来看是非阻塞的。
为什么进程的时间略多于线程呢?
因为进程切换时的上下文切换花费时间高于线程。
进程在上下文切换是需要保存当前进程的寄存器,内存状态,所以耗时比较长。而线程切换耗时较少,所以多线程略快于多进程。
协程
从结果来看,协程似乎是最快的。虽然这里数据量较少,但是从理论分析可以得知这样的结论:协程是用户态的并发,没有cpu调度,协作式的cpu机制比线程的cpu竞争机制要快,因为协程中cpu一直在用户态,没有发生切换,对比线程少了10次切换。
结论
由此可以看出在IO频繁的业务中适合用多线程、协程
对比
类型 | 特点 | 优点 | 缺点 |
---|---|---|---|
同步 | – | 同步阻塞的网络交互方式,效率低十分低下 | |
多进程 | 使用多个cpu核心执行任务 | 有效减少同步过程的时间阻塞 | 进程切换开销较大,由于内存资源的限制,一个任务开启的进程数有限 |
多线程 | 使用一个cpu核心开启多个线程执行 | 执行任务更加轻量级,支持数百到数千的数量规模。遇到阻塞任务自动让出GIL,可以有效解决阻塞 | GIL让多核cpu同时只能有一个工作。调度策略是抢占式,需要业务控制 |
协程 | 一个线程下的并发,没有cpu切换 | 没有cpu调度,使用系统的事件通知,耗时最少 | 协程并发需要相应模块的支持,目前模块异步的支持较少 |
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196501.html