开始

​ 之前一直在做那个rProxy的项目,后来发现,服务端不用协程或者异步编程这样的手段是不行的,最主要的问题就是对于每个http请求都对应一个线程,这个开销非常大。对于一个网页而言,四五十个http请求已经是非常常见的事情了,如果有很多个客户端,一下子线程数可能得有几百个。而Python的多线程众所周知的虚假。

​ 所以我就会考虑使用异步套接字来做。

​ 在异步套接字中会需要一个主循环用来管理套接字,手动管理比较麻烦,后来了解到了协程。发现这个就是非常适合做这个事情的啊。

​ 而Python的库就是asyncio 官方文档在这里

​ <协程与任务 — Python 3.9.1 文档>

​ 不过后面又发现了一个更神奇的东西,就是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] 协程学习过程的更多相关文章

  1. Python 协程总结

    Python 协程总结 理解 协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是 ...

  2. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

  3. 终结python协程----从yield到actor模型的实现

    把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...

  4. 从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 ...

  5. 关于python协程中aiorwlock 使用问题

    最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...

  6. 用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  7. [转载] Python协程从零开始到放弃

    Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC   2017-10-09  3,973   Author: lightless@Meili-inc Date: 2017100 ...

  8. 00.用 yield 实现 Python 协程

    来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...

  9. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  10. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

随机推荐

  1. 使用svn.externals(外链)提升美术多个svn目录的svn up速度

    svn up多个目录耗时大 svn上的美术资源项目,在打包机上对一个很久没有变化的目录进行svn up也是需要消耗不少时间的,特别打包时需要对多个目录进行svn up,比如空跑54个目录的svn up ...

  2. MongoDB 选型介绍

    什么是 MongoDB 前言 MongoDB 的主要特性 MongoDB 对比关系型数据库 MySQL 什么时候考虑 MongoDB 参考 什么是 MongoDB 前言 MongoDB 是一个开源.高 ...

  3. 深入浅出Java多线程(三):线程与线程组

    「引言」 大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第三篇内容:线程与线程组.大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在现代软件开发中,多线程编程已成 ...

  4. Intel Arrow Lake处理器还是8+16 24核心:接口换LGA1851

    Intel已经确认,将在今年内发布未来两代处理器Arrow Lake.Lunar Lake,其中前者将弥补Meteor Lake的不足,同时用于笔记本.桌面.服务器,现在它的核心规格流出了. 这份曝光 ...

  5. 史上最大电池!小米智能家庭屏Pro 8图赏

    今天小米智能家庭屏 Pro 8正式开售,集智能家居中控,智能网关以及娱乐教育三大功能为一体,首发749元. 它是一款全新的智能生态产品中控屏,配备了7500mAh大容量电池以及通用性更好的USB Ty ...

  6. Vite4+Typescript+Vue3+Pinia 从零搭建(2) - ts配置

    项目代码同步至码云 weiz-vue3-template 关于tsconfig的配置字段可查看其他文档,如 typeScript tsconfig配置详解 tsconfig.json 文件修改如下: ...

  7. delphi TThread.WaitFor 用法

    在 Delphi 中,TThread.WaitFor 方法用于等待一个线程完成执行.当你创建一个线程并希望主线程(或其他线程)等待这个线程结束时,你可以使用这个方法. 以下是 TThread.Wait ...

  8. HBase-通过外部表将Hive数据写入到HBase

    a) 准备测试数据 这里准备的csv文件data_test.csv,内容没用''包裹,逗号作为列分隔符 171301,燕青,男,27,发展部 171207,武松,男,39,开发部 171307,李逵, ...

  9. Pandas数据合并

    目录 1) 在单个键上进行合并操作 2) 在多个键上进行合并操作 使用how参数合并 1) left join 2) right join 3) outer join(并集) 4) inner joi ...

  10. CF1433E Two Round Dances 题解

    题目传送门 前置知识 圆排列 解法 \(\dfrac{Q_{n}^{\frac{n}{2}}Q_{\frac{n}{2}}^{\frac{n}{2}}}{A_{2}^{2}}\) 即为所求. 同时因为 ...