[Python] 协程学习过程
开始
之前一直在做那个rProxy的项目,后来发现,服务端不用协程或者异步编程这样的手段是不行的,最主要的问题就是对于每个http请求都对应一个线程,这个开销非常大。对于一个网页而言,四五十个http请求已经是非常常见的事情了,如果有很多个客户端,一下子线程数可能得有几百个。而Python的多线程众所周知的虚假。
所以我就会考虑使用异步套接字来做。
在异步套接字中会需要一个主循环用来管理套接字,手动管理比较麻烦,后来了解到了协程。发现这个就是非常适合做这个事情的啊。
而Python的库就是asyncio 官方文档在这里
不过后面又发现了一个更神奇的东西,就是gevent这个库,项目在这里
<GitHub - gevent/gevent: Coroutine-based concurrency library for Python>
pip不能安装的话whl包在这里Python Extension Packages for Windows - Christoph Gohlke (uci.edu)
协程
线程的话,已经很熟悉了。对于需要大量IO操作的程序,基本上首选多线程。但是线程也有一些缺点,例如说需要考虑线程安全问题,线程间切换也会损耗性能。
非常常见的现象就是,使用Python多线程写爬虫我的电脑大约80线程就已经是极限了,再增加线程也不会有什么速度上的加快了。
此时,协程就出现了
一个线程可以包含很多的协程,而协程之间的切换不需要经过系统,不需要进行内核态的切换。还有各种复杂的保存现场。
学习过程
这里就记录一下对协程的学习过程,主要是两种。
一个是以asyncio为主的协程
另一个是以第三方库gevent为主的协程
## asyncio
asyncio是最先了解到的技术,可以用于实现协程。一个简单的Demo如下。
import asyncio as ayc
i = 0
async def p(c):
global i
while True:
print(f"t{c}: {i}")
i+=1
await ayc.sleep(0)
async def fun():
t1 = ayc.create_task(p(1))
t2 = ayc.create_task(p(2))
await t1
await t2
def main():
ayc.run(fun())
if __name__ == "__main__":
main()
这个例子只是对一个公共变量进行叠加,结果

运行之后能够看到数字递增,而且非常有序,t1,t2两个协程分工交替出现
而多线程不加锁的情况下,很容易出现以下的情况。也就是出现t1 t2时间分配不均匀的情况,并且线程不安全 89914出现在了89916后面

多线程代码如下
import threading
i = 0
def fun(c):
global i
while True:
print(f"t{c}: {i}")
i+=1
def main():
threading.Thread(target=fun,args=(1,)).start()
threading.Thread(target=fun,args=(2,)).start()
if __name__ == "__main__":
main()
目前到这里就没有问题,但是等我实际写代码的时候,发现一个问题。
也就是,如果一个IO操作阻塞,则整个线程卡死,导致协程不能正常切换。例如下面的代码
import requests
import asyncio
import time
urls = ["https://www.baidu.com","https://www.cnblogs.com/","https://blog.csdn.net/"]
async def getHtml(url):
stime = time.time()
r = requests.get(url)
print(f"{url}, {r.status_code}, {time.time() - stime}")
async def main():
tasks = []
for url in urls:
tasks.append(asyncio.create_task(getHtml(url)))
stime = time.time()
await asyncio.wait(tasks)
print(f"Done, {time.time() - stime}")
if __name__ == "__main__":
asyncio.run(main())
代码作用是分别请求三个域名,记录响应时间以及总时间
结果如下,注意,此时虽然用了协程,但是总时间却是三次请求时间之和。

原因就是,直接使用asyncio实现协程并不会监听IO阻塞情况,也就是在requests.get()的时候,协程没有切换。导致整个线程阻塞。
所以实际的执行流依旧是串行执行,那么协程就毫无意义。
aiohttp
而这个问题存在的原因就在于,requests这个库是同步库,底层是同步socket。对应的,另一个名为 aiohttp的第三方库是基于asyncio开发的http库可以解决这个问题。
aiohttp官方文档如下
<Welcome to AIOHTTP — aiohttp 3.7.3 documentation>
一个Demo如下
import aiohttp
import asyncio
import time
urls = ["https://www.baidu.com","https://www.cnblogs.com/","https://blog.csdn.net/"]
async def getHtml(url):
print(f"请求: {url}")
stime = time.time()
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
print(f"status: {response.status}, {time.time() - stime}")
# html = await response.text()
# print(f"Body: {html[:20]}")
async def main():
tasks = []
for url in urls:
tasks.append(asyncio.create_task(getHtml(url)))
await asyncio.wait(tasks)
print("Done")
asyncio.run(main())
最后结果如下

可以看到总花费时间近似于最长请求时间。也就是完成了异步请求
那么,到这里似乎就已经圆满了。但是,我还了解到了gevent这个库
gevent
直接贴代码
from gevent import monkey; monkey.patch_all()
import gevent
import requests
import asyncio
import time
urls = ["https://www.baidu.com","https://www.cnblogs.com/","https://blog.csdn.net/"]
def getHtml(url):
stime = time.time()
r = requests.get(url)
print(f"{url}, {r.status_code}, {time.time() - stime}")
def main():
tasks = []
for url in urls:
tasks.append(gevent.spawn(getHtml,url))
stime = time.time()
gevent.joinall(tasks)
print(f"Done, {time.time() - stime}")
if __name__ == "__main__":
main()
结果如下

看到上面的结果和使用aiohttp库一样,并且依旧是用了requests这个库,而前面说过,requests是同步库。。。 是不是很神奇!是的,给我惊讶到了。
然后原理也给了解了一下, <关于 python gevent 架框 作为 TCP服务器 的 代码问题 , 每个 socket 的 消息 接收 是否有使用 事件监听回调的方法呢? - 知乎 (zhihu.com)>
总的来说,就是替换了Python自己的socket实现,把socket设置成了异步。绝了~
[Python] 协程学习过程的更多相关文章
- Python 协程总结
Python 协程总结 理解 协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是 ...
- day-5 python协程与I/O编程深入浅出
基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1. 什么是协程(以下内容来自维基百 ...
- 终结python协程----从yield到actor模型的实现
把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...
- 从yield 到yield from再到python协程
yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...
- 关于python协程中aiorwlock 使用问题
最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...
- 用yield实现python协程
刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...
- [转载] Python协程从零开始到放弃
Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC 2017-10-09 3,973 Author: lightless@Meili-inc Date: 2017100 ...
- 00.用 yield 实现 Python 协程
来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...
- python协程详解
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
- Python协程与Go协程的区别二
写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...
随机推荐
- React中css的module
处理css全局作用 现在有这样一个场景: A页面和B页面都有一个相同的类名 我们在A页面中有引入css. B页面没有css 在我们切换A和B页面的时候. A页面的css也作用在了B页面. 我们只希望A ...
- 手写promise完成异常处理和状态只能够修改一次
8.处理 Promise 抛出的异常 <script src="./Promise.js"></script> <script type=" ...
- P7900 [COCI2006-2007#2] SJECIŠTA_题解
[COCI2006-2007#2] SJECIŠTA_题解 rt 我们来看一下题目描述 考虑一个有 \(n\) 个顶点的凸多边形,且这个多边形没有任何三个(或以上) 的对角线交于一点. 这句话什么意思 ...
- 基于Basic auth 的一个C# 示例
最近在做公司的一个项目时,客户需要我们定时获取他们矩阵系统的数据.在与客户进行对接时,提到他们的接口使用的目前不常用的BASIC 认证.天呢,它好不安全,容易被不法人监听,咋还在使用呀.但是没办法呀, ...
- 金融领域:产业链知识图谱包括上市公司、行业和产品共3类实体,构建并形成了一个节点10w+,关系边16w的十万级别产业链图谱
金融领域:产业链知识图谱包括上市公司.行业和产品共3类实体,构建并形成了一个节点10w+,关系边16w的十万级别产业链图谱 包括上市公司所属行业关系.行业上级关系.产品上游原材料关系.产品下游产品关系 ...
- Windows Server ISO原版镜像文件下载(2023年04月)
Windows Server 2022 (updated April 2023) (x64) - DVD (Chinese-Simplified) 链接:https://pan.baidu.com/s ...
- 关于Delphi TabOrder 更快捷的设置 方法
今天才发现 delphi IDE 有设置 TabOrder 的 更加 快捷的方法,以前 总觉的 设计 起来 好麻烦.现在 终于 解放了.... ---------------------------- ...
- 《ASP.ENT Core 与 RESTful API 开发实战》-- (第5章)-- 读书笔记(下)
第 5 章 使用 Entity Framework Core 5.4 重构 Controller 和 Action 重构 AuthorController 构造函数重构 public IMapper ...
- AgileConfig-1.9.0 发布,支持 MongoDB 存储
Hello 大家好,先祝福大家新年快乐. AgileConfig 1.9.0 版本终于赶在农历年前发布了. Mongodb 当前做为一款非常成熟的 Nosql 产品,已经有越来越多的产品或项目基于它来 ...
- win10远程桌面连接,使用正确的用户名和密码仍然不能成功连接
最近笔记本重置后,台式使用"远程桌面连接"远程笔记本失败了,总是提示"登录没有成功". 开始自查:win10专业版,允许远程的相关设置也都开了,连接的ip正确, ...