python开发基础篇:七:协程简介
1:协程简介:https://www.cnblogs.com/Eva-J/articles/8324673.html
操作系统中:
进程是资源分配的最小单位,
线程是CPU调度的最小单位。
按道理来说我们已经算是把cpu的利用率提高很多了。但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程、创建线程、以及管理他们之间的切换
效率的追求不断提高,基于单线程来实现并发效果
对于线程来说始终是一个线程,在一个线程之间切换(切换速度快,不涉及锁和数据切换的问题,数据都在一个寄存器和栈里)
协程的切换与正常程序的执行:
使用yield迭代器实现两个程序交叉切换:两个函数交替实现的效果 def func1(): print(1) yield print(3) yield def func2(): g = func1() next(g) print(2) next(g) print(4) func2()
使用生成器模拟生产者消费者模型: def consumer(): while 1: n = yield print(f"消费了数据:{n}") def producer(): g = consumer() next(g) for i in range(10): print(f"生产了数据:{i}") g.send(i) producer() 一个线程之间两个任务切换执行,yield自带保存函数执行的状态,实现上下文的切换
pip安装greenlet模块:在一个单线程中提供切换状态的模块
pip安装Gevent模块
greenlet模块实现交互切换 from greenlet import greenlet def eat1(): print("吃鸡腿1") g2.switch() print("吃鸡腿2") g2.switch() def eat2(): print("吃饺子1") g1.switch() print("吃饺子2") g1 = greenlet(eat1) g2 = greenlet(eat2) g1.switch() # 在哪个地方switch就在哪个地方转 打印: 吃鸡腿1 吃饺子1 吃鸡腿2 吃饺子2
gevent内部也是这么做切换的,gevent切换内部封装的就是greenlet模块,greelet是底层模块
暂时这个实例没有规避到等待和I/O等待的时间
greenlet模块只能做切换,并不能规避掉程序当中的I/O时间
yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换 单纯地切换反而会降低运行效率
#串行执行 import time def consumer(res): '''任务1:接收数据,处理数据''' pass def producer(): '''任务2:生产数据''' res=[] for i in range(10000000): res.append(i) return res start=time.time() #串行执行 res=producer() consumer(res) #写成consumer(producer())会降低执行效率 stop=time.time() print(stop-start) #1.5536692142486572 #基于yield并发执行 import time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() #基于yield保存状态,实现两个任务直接来回切换,即并发的效果 #PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. producer() stop=time.time() print(stop-start) #2.0272178649902344
在代码之间切换执行反而会降低效率,基于yield需要保存状态(greelet更慢),实现两个任务的切换,更耗费时间,保存状态再去切换-----切换反而让效率更低了
如果在同一个程序中有I/O的情况下才切换的话会让效率提供很多,
yield和greenet都不能在切换的时候 规避I/O时间
Gevent:能够规避I/O时间的模块,协程模块
2:gevent协程实例
import gevent def func1(): print(1) gevent.sleep(1) # gevent.sleep和time.sleep效果类似,都是睡1s print(2) def func2(): print(3) gevent.sleep(1) print(4) g1 = gevent.spawn(func1) # 发布任务并且执行 g2 = gevent.spawn(func2) g1.join() g2.join() gevent.sleep(5) g1任务和g2任务和主协程任务相当于3个任务完全异步,所以需要g1.join和g2.join阻塞主协程 gevent:遇见他认识的io会自动切换的模块 gevent.sleep:自带的sleep方法,他自己认识的I/O阻塞,time.sleep他就不认识了他就不会切换的
join和joinall的使用 import gevent def func1(): print(1) gevent.sleep(1) # gevent.sleep和time.sleep效果类似,都是睡1s print(2) def func2(): print(3) gevent.sleep(1) print(4) g1 = gevent.spawn(func1) # 发布任务并且执行 g2 = gevent.spawn(func2) # g1.join() # g2.join() gevent.joinall([g1, g2]) g1.join()+join() 等同 event.joinall([g1, g2])
gevent自带的一些I/O他就认识,不自带的I/O操作就不认识, I/O操作:sleep,socket,网络url请求(requests和urllib等)一般都是这些I/O操作 引入这些I/O模块之前先导入monkey模块 from gevent import monkey monkey.patch_all() 这两行代码会把下面的导入的所有模块的I/O都打成一个包,就认识sleep,和socket这些I/O操作了,
这两行代码必须写前面,比如想识别time模块的I/O time.sleep必须写在import time模块的前面,顶行写就行 from gevent import monkey;monkey.patch_all() import gevent import time def func1(): print(1) time.sleep(1) print(2) def func2(): print(3) time.sleep(1) print(4) g1 = gevent.spawn(func1) # 发布任务并且执行 g2 = gevent.spawn(func2) gevent.joinall([g1, g2]) 现在只能识别gevent内置模块和导入模块的一些I/O 现在遇到的主要的I/O是:time模块,socket模块,urllib和request网络请求,open打开文件(需要操作系统打开给一个文件句柄) input和打开文件是内置的方法,from gevent import monkey;monkey.patch_all()这行代码无法让他遇到input和文件操作I/O跳转,只能作用于导入的包里面的I/O
gevent封装了greenlet的模块在内部帮他实现切换工作,gevent在切换程序的基础上又实现了规避I/O
from gevent import monkey;monkey.patch_all() import time import gevent from threading import currentThread def func1(): print(currentThread().name, currentThread()) # Dummy-1 print(1) time.sleep(1) print(2) def func2(): print(currentThread().name, currentThread()) # Dummy-2 print(3) time.sleep(1) print(4) g1 = gevent.spawn(func1) # 发布任务并且执行 g2 = gevent.spawn(func2) gevent.joinall([g1, g2]) currentThread().name查看线程名字打印: Dummy-1和Dummy-2 Dummy:虚设的,假的,虚拟假设的线程,实际上 Dummy-1和Dummy-2还在同一个线程之内的
协程执行和同步程序执行的效率对比 from gevent import monkey;monkey.patch_all() import time import gevent def task(args): time.sleep(1) print(args) def sync_func(): # 同步 for i in range(10): task(i) def async_func(): # 异步 g_l = [] for i in range(10): g_l.append(gevent.spawn(task, i)) # 发起一个协程任务,给协程任务传参数 gevent.joinall(g_l) start_time = time.time() sync_func() print(time.time() - start_time) # 10.118812084197998 start_time = time.time() async_func() print(time.time() - start_time) # 1.0129859447479248 有I/O的情况下使用协程效果比较明显,整体程序都是在一个线程内执行的 I/O在程序当中对程序效率影响很大
3:使用协程爬取网页
from gevent import monkey;monkey.patch_all() # 让request里面的I/O阻塞能够被捕捉到 import gevent import requests import time """ 爬取网页 10个网页 协程函数去发起10个网页的爬取任务: 10个网页get url需要一定时间响应,协程函数一个网页去爬取过程当中遇到网络延时或者等待I/O就马上去执行下一个任务了 上面的网页等着,而下面的网页可以开始都执行上了,这样可以复用一些I/O时间,协程的意义 """ url_lis = [ "https://www.baidu.com", "https://www.sogou.com", "https://www.hao123.com", "https://www.jd.com", "https://www.taobao.com", "https://www.sohu.com", "https://www.qq.com", "https://www.python.org", "https://www.cnblogs.com", "https://www.apache.org/", ] def get_url(url): res = requests.get(url) # 这里可以复用等待时间,发起get请求就陷入I/O等待了 # return url, res.status_code, len(res.text) print(url, res.status_code, len(res.text))
start_time = time.time() for url in url_lis: ret = requests.get(url) print(url, ret.status_code, len(ret.text)) print(time.time() - start_time) # 2.8345634937286377 start_time = time.time() g_lis = [] for url in url_lis: g_lis.append(gevent.spawn(get_url, url)) gevent.joinall(g_lis) print(time.time() - start_time) # 1.0743591785430908 协程发送get请求不等结果(识别到这个地方有网络延时),马上切换到另外一个任务,利用等待的时间 协程在执行多个任务,多I/O的情况下才能有优势
4:使用gevent协程实现socket聊天
server.py from gevent import monkey;monkey.patch_all() import gevent import socket def talk(conn): while 1: ret = conn.recv(1024).decode("utf8") print(ret) conn.send(ret.upper().encode("utf8")) conn.close() sk = socket.socket() sk.bind(("127.0.0.1", 9999)) sk.listen() while 1: conn, addr = sk.accept() gevent.spawn(talk, conn) # 聊天任务放到线程,进来一个链接开启一个协程去聊天 sk.close() 基于一个线程的协程,能同时接收多个客户端链接 协程的开启毫无压力:一个协程可以接收500个并发甚至更多 协程数目:(cpu_count + 1)*cpu_count*5*500 这样一台4核机器接收5w的并发 gevent: 1:处理导入进来的模块中间存在I/O的情况才能帮忙节省时间(网络延迟相关的,socket相关的这些都能识别到) 一般爬虫和网络编程用到协程比较多 2:
client.py import socket import time import threading def my_client(): sk = socket.socket() sk.connect(("127.0.0.1", 9999)) while 1: sk.send(b"hi") ret = sk.recv(1024).decode("utf8") print(ret) time.sleep(1) sk.close() for i in range(500): t = threading.Thread(target=my_client).start()
5:域名
1:域名比较好记
2:服务端后端服务几百台机器,不知道具体访问哪个机器的ip,只知道具体访问的域名
nginx:反向代理
客户端请求先访问到一个服务器上(一组服务器),nginx也可能有几台机器跑的都是nginx,请求访问到其中的一台
niginx机器就跑一个服务,他来做转发,比如客户端某个机器访问www.baidu.com/zhishi这个域名,
其实是访问nginx机器上,niginx机器根据请求后面的参数什么的找到真正对应的机器,对应服务的机器也可能很多台
nginx随机分配一台(内部自己的算法分配,负载均衡),消息的转发,一般代理的机器少,后台服务的机器多,
代理管理着很多项目做转发,nginx反向代理一台机器接收处理的并发送需要支持5w个(nginx运用:进程+线程+协程),
对一条线程在I/O处理上做了复用,类似协程,nginx对I/O处理很好所以能接收高并发,几个代理就能把服务器的资源分配都做好
nginx:利用高并发编程十分优秀的程序
分类:
Python篇【基础语法+进阶】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix