Python异步编程并发比较之循环、进程、线程、协程

服务端
现在有一个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调度,使用系统的事件通知,耗时最少 | 协程并发需要相应模块的支持,目前模块异步的支持较少 |
Python异步编程并发比较之循环、进程、线程、协程的更多相关文章
- Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程
1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...
- 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型
本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...
- Python 进程线程协程 GIL 闭包 与高阶函数(五)
Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...
- python自动化开发学习 进程, 线程, 协程
python自动化开发学习 进程, 线程, 协程 前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...
- 进程&线程&协程
进程 一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...
- python的进程/线程/协程
1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有 ...
- python-socket和进程线程协程(代码展示)
socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...
- 并发 并行 进程 线程 协程 异步I/O python async
一些草率不精确的观点: 并发: 一起发生,occurence: sth that happens. 并行: 同时处理. parallel lines: 平行线.thread.join()之前是啥?落霞 ...
- python基础(16)-进程&线程&协程
进程之multiprocessing模块 Process(进程) Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建. 介绍 初始化参数 Process([group [, t ...
- day30 网络编程 之进程,线程,协程
进程点进去 线程点进去 协程点进去 什么是进程 进程(有时称为重量级进程)是一个执行中的程序.每个进程都拥有自己的地址空间.内存.数据栈以及其他用于跟踪执行的辅助数据.同一个程序执行两次,属于是两个不 ...
随机推荐
- 淘天Java一面,难度适中!(上篇)
1.公司介绍 淘天集团是阿里巴巴集团全资拥有的业务集团,全球领先的科技商业公司. 淘天集团以淘宝 APP 为主要服务载体,构建国内国际供给.线上线下场景.远场近场履约相结合的商业矩阵,汇聚数十万全球和 ...
- [WPF]标记扩展(Markup Extension)
XAML是基于XML的语言,其遵循并扩展了XML的语法规则.其中一项扩展就是标记扩展(Markup Extension),比如我们经常使用的绑定Binding和x:Type. 什么是标记扩展 标记扩展 ...
- typeid关键词
typeid是c++的关键字,typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义) ISO C++标准并没有确切定义type_info,它的确 ...
- 提高cin cout的速度
ios::sync_with_stdio(false) :用来关闭iostream与stdio的同步,从而提高 cin cout 的效率,但是就不能再用 scanf printf 了,因为不关闭之前 ...
- 🔥🔥通过访问URL地址,5分钟内渗透你的网站!很刑很可拷!
今天我来带大家简单渗透一个小破站,通过这个案例,让你深入了解为什么很多公司都需要紧急修复各个中间件的漏洞以及进行URL解析拦截等重要操作.这些措施的目的是为了保护网站和系统的安全性.如果不及时升级和修 ...
- 配置tabBar导航菜单与open跳转差异
"tabBar": { "color": "#333", "selectedColor": ...
- 基于Docker Desktop搭建Kafka集群并使用Java编程开发
一.引言 前段时间因课业要求使用Docker Desktop 部署Kafka集群并编写生产者消费者程序,折磨了我好几天,在查找大量资料后终于是把整个集群搭建完成了.现在我想要分享其中搭建的历程,希望能 ...
- Celery周期性任务定义beat
通过celery beat可以使用周期性任务的定义. https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html 周期性任务bea ...
- 整一个工具类【根据URL地址获取file文件对象】
整一个工具类[根据URL地址获取file文件对象] 直接将网络url文件转换为file对象 import java.io.*; import java.net.URL; public class Im ...
- 解决vps掉线问题
解决vps掉线问题 常见现象 在有时候遇到网络或者断电等一系列突发状况时,可能会导致在传输大文件或是好不容易拿到一个session断连了,所以有了这次学习解决这个问题的记录 场景复现 这里直接用kal ...