服务端

现在有一个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异步编程并发比较之循环、进程、线程、协程的更多相关文章

  1. Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程

    1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...

  2. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  3. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  4. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  5. 进程&线程&协程

    进程  一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...

  6. python的进程/线程/协程

    1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有 ...

  7. python-socket和进程线程协程(代码展示)

    socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...

  8. 并发 并行 进程 线程 协程 异步I/O python async

    一些草率不精确的观点: 并发: 一起发生,occurence: sth that happens. 并行: 同时处理. parallel lines: 平行线.thread.join()之前是啥?落霞 ...

  9. python基础(16)-进程&线程&协程

    进程之multiprocessing模块 Process(进程) Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建. 介绍 初始化参数 Process([group [, t ...

  10. day30 网络编程 之进程,线程,协程

    进程点进去 线程点进去 协程点进去 什么是进程 进程(有时称为重量级进程)是一个执行中的程序.每个进程都拥有自己的地址空间.内存.数据栈以及其他用于跟踪执行的辅助数据.同一个程序执行两次,属于是两个不 ...

随机推荐

  1. C#判断字符串的显示宽度

    C#判断字符串的显示宽度 起因: 公司有一个使用项目使用HTML转换为PDF,其中有一个表格,表格的最后一列中的单元格,其字符串超长后会被丢弃,而不是换行到下一行展示(HtmlToPdf渲染引擎导致的 ...

  2. 小景的Dba之路--压力测试和Oracle数据库缓存

    小景最近在做系统查询接口的压测相关的工作,其中涉及到了查询接口的数据库缓存相关的内容,在这里做一个汇总和思维发散,顺便简单说下自己的心得: 针对系统的查询接口,首次压测执行的时候TPS较低,平均响应时 ...

  3. MyBatis核心流程

    摘要 mybatis的核心流程,主要是对于主线的一个探索.目的是对于整个mybatis流程有个初步的印象 核心流程 核心流程搞懂:主线,涉及的模块不深究.再去基础支持层,再回来核心. /** * My ...

  4. CatCatCat

    拿到题目没有思路,查看了题解 附件 放到kali中用string找flag,得到一个密钥,可以用来下面的解密 打开我养了-- 发现里面以U2F开头,结合txt名称"我养了一只叫兔子的91岁的 ...

  5. C语言输入若干个正整数(输入-1为结束标志),要求按输入数据的逆序建立单链表并输出。

    /* 开发者:慢蜗牛 开发时间:2020.6.11 程序功能:逆序建立链表,顺序输出 */ #include<stdio.h> #include<malloc.h> #defi ...

  6. 【.NET】控制台应用程序的各种交互玩法

    老周是一个不喜欢做界面的码农,所以很多时候能用控制台交互就用控制台交互,既方便又占资源少.有大伙伴可能会说,控制台全靠打字,不好交互.那不一定的,像一些选项类的交互,可以用键盘按键(如方向键),可比用 ...

  7. MySQL运维10-Mycat分库分表之一致性哈希分片

    一.一致性哈希分片 一致性哈希分片的实现思路和我们之前介绍的水平分表中的取模分片是类似的.只不过取模分片,采用的是利用主键和分片数进行取模运算,然后根据取模后的结果,将数据写入到不同的分片数据中.但是 ...

  8. The fourth day learning summary

    一.for 循环循环就是重复做某件事,for循环是python提供第二种循环机制(第一种是while循环),理论上for循环能做的事情,while循环都可以做.目的:之所以要有for循环,是因为for ...

  9. X410的白嫖方案

    微软商店下的x410要收费试用也就几天,记录白嫖方案.配置和使用跳转到上一篇文章:Windows下使用图形化的Havoc C2 编译运行 GitHub上start最多的是这个仓库,但是已经很久没有维护 ...

  10. CentOS7 安装Python3.9以上版本时。编译报错,原因是openssl版本低

    openssl-1.1.1安装 1.前因 python 导入clickhouse_driver需要import ssl和_ssl,报错 File"/home/oracle/python3/l ...