一、协程存在的原因
因为想要在单线程内实现并发的效果(因为CPython有GIL锁,限制了在同一个时间点,只能执行一个线程,所以想要在执行一个线程的期间,充分的利用CPU的性能,因此想在单线程内实现并发的效果)
并发 :切换 + 保存状态 (yield 只能单纯的实现状态的保存和切换函数)
yield不能实现:当某一个函数中遇到IO阻塞时,自动的切换到另一个函数去执行
目标是:当某一个函数中遇到IO阻塞时,程序能自动的切换到另一个函数去执行
如果能实现这个功能,那么每个函数都是一个协程。
CPU是为什么要切换?
1 、因为某个程序阻塞了
2、因为某个程序时间片用完了
想要实现单线程的并发,就要解决在单线程内,多个任务函数中,某个任务函数遇见IO操作,马上自动切换到其他任务函数去执行。
二、协程
1、含义:是一个比线程更加轻量级的单位,是组成线程的各个函数,协程本身没有实体
2、greenlet模块:能简单的实现函数与函数之间的切换,但是遇到IO操作,不能自动切换到其他函数中。
(1)注册一下函数func,将函数注册成一个对象f1
f1 = greenlet(func)
(2)调用func,使用f1.switch(),如果func需要传参,就在switch这里传即可
3、gevent模块:可以实现在某函数内部遇到IO操作,就自动的切换到其他函数内部去执行
gevent不识别别的IO操作,例如:time.sleep(1)就不识别,不会进行函数的切换
我们可以通过mokey来解决gevent不识别其他IO操作的现象
mokey.patch_all() 可以gevent识别大部分常用的IO操作
g = gevent.spawn(func,参数) 注册一下函数func,返回一个对象g
gevent.join(g) 等待g指向的函数func执行完毕,如果在执行过程中,遇到IO,就切换
gevent.joinall([g1,g2,g3]) 等待g1,g2,g3指向的函数func执行完毕
import gevent
import time
def func():
print(111)
time.sleep(1) # gevent不识别除了自己以外的IO操作,
gevent.sleep(1) # 执行被阻塞,切换函数去执行别的函数
print(222)
def func1():
time.sleep(1)
print(333)
gevent.sleep(1)
print(444)
g1 = gevent.spawn(func) # 实例化一个对象
g2 = gevent.spawn(func1)
g2.join()
g1.join() # 把等待g1的函数执行完毕
串行和并发的效率对比
from gevent import monkey
monkey.patch_all() # gevent可以识别time.sleep()IO操作,是一个阻塞。
import time
import gevent
def func(i):
time.sleep(1)
print(i)
start = time.time()
for i in range(10):
func(i)
print(time.time() - start)
start1 = time.time()
l = []
for i in range(10):
g = gevent.spawn(func,i)
l.append(g)
gevent.joinall(l)
print(time.time() - start1)
三、进程、线程、协程区别
计算密集用多进程,可以充分的利用多核CPU的性能
IO密集用多线程,(注意:协程是单线程的)
多线程和协程的区别是 :
线程是由操作系统调度,控制
协程是由程序员自己调度,控制
四、IO多路复用
阻塞IO
非阻塞IO
多路复用IO
异步IO python实现不了,但是有tornado框架,天生自带异步
五、select 和 poll 和epoll 的区别
select 和poll有一个共同的机制,都是采用轮询的方式去询问内核,有没有数据准备好了。
select有一个最大监听事件的限制,32位机限制1024,64位机限制2048
poll没有,理论上poll可以开启无限大,1G内存大概可以开10W个事件去监听
epoll是最好的,采用的是回调机制,解决了select和poll共同存在的问题
而且epoll理论上也可以开启无线多个监听事件
# 基于select的网络IO模型
import select
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
del_l = []
rlist = [sk] # 是用来让select帮忙监听的 所有接口
# select:Windows/Linux 是监听事件有没有数据到来
# poll:Linux 也可以做select的工作
# epoll:Linux 也可以做类似的工作
while 1:
r,w,x = select.select(rlist,[],[]) # 传参给select,当rlist列表中哪个接口有反应,就返回给r这个列表
if r:
for i in r: # 循环遍历r,看看有反应的接口到底是sk,还是conn
if i == sk:
'''sk有数据要接收,代表着有客户端要来连接'''
conn,addr = i.accept()
rlist.append(conn) # 把新的客户端的连接,添加到rlist,继续让select帮忙监听
else:
try:
msg = i.recv(1024).decode('utf-8')
if not msg:
'''客户端关闭了连接'''
del_l.append(i)
i.close()
else:
i.send(msg.upper().encode('utf-8'))
except ConnectionResetError:
pass
if del_l: # 删除那些主动断开连接的客户端的conn
for i in del_l:
rlist.remove(i)
del_l = []