协程

一. 协程: 单线程实现并发(保存状态加切换)

协程本质上就是一个线程,以前线程任务的切换是由操作系统控制的,
遇到I/O自动切换,现在我们用协程的目的就是较少操作系统切换的开销
(开关线程,创建寄存器、堆栈等,在他们之间进行切换等),
在我们自己的程序里面来控制任务的切换。
优点: 切换速度快 单线程内就可以实现并发的效果,最大限度利用CPU
缺点: 无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
特点:
1)必须在只有一个单线程里实现并发
2)修改共享数据不需加锁
3)用户程序里自己保存多个控制流的上下文栈
4)附加:一个协程遇到IO操作自动切换到其它协程
(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

二. Greenlet
使用greenlet完成切换
from greenlet import greenlet
g = greenlet(函数)
g.swith() 切换 不能IO自动切换
三. gevent
g1 = gevent.spawn(函数1)
g2 = gevent.spawn(函数2)
g1.join() 等待g1结束,上面只是创建协程对象,join才是去执行
g2.join() / 两步合并: gevent.joinall([g1,g2])
g1.value 拿到函数1的返回值
注意:
要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
四. 非阻塞IO
server.setBlocking(False) # 将阻塞变为非阻塞 遇到原本应阻塞的会报错
lst1 = [] 存放conn连接
dic = {} 存放所有已经有了请求数据的conn连接
try:
会报错的原本应阻塞的程序
except BlockingIOError:
del_lst = []
for conn in lst1:
ret = 接收数据
if not ret:
conn.close()
del_lst.append(conn) # 将断开连接的conn放在新列表中,准备删除
dic[conn] = ret.upper() # 将接收的数据已字典的形式存入 准备发送数据用
del_wlst = []
for conn,data in dic.items():
conn.send(data)
del_wlst.append(conn)
for conn in del_lst:
lst1.remove(conn)
for conn in del_wlst:
dic.pop(conn)
五. IO多路复用
server.setBlocking(False) # 将阻塞变为非阻塞 遇到原本应阻塞的会报错
# 存放所有有变化的连接对象,server/conn
r_lst = [server]
# 存放客户端发送过来的消息
r_data = {}
# 等待写对象
w_lst = []
# 存放要返回给客户端的消息
w_dit = {}
# # 被触发的(有动静的)套接字(服务器套接字)返回给了rl这个返回值里面
r1, w1, x1 = select.select(r_lst, w_lst, [], 0.5)

for sock in r1:
if sock == server:
conn, addr = sock.accept()
r_lst.append(conn)
else:
try:
试着接收数据
sock.recv(1024)
# 没有数据的时候,我们将这个连接关闭掉,并从监听列表中移除
if not data:
sock.close()
r_lst.remove(sock)
continue
# 将接受到的客户端的消息保存下来
r_data[sock] = data.decode("utf-8")
# 将客户端连接对象和这个对象接收到的消息加工成返回消息,并添加到w_dit这个字典里面
w_dit[sock] = data.upper()
# 需要给这个客户端回复消息的时候,我们将这个连接添加到w_lst写监听列表中
w_lst.append(sock)
for sock in w1:
sock.send(w_dit[sock])
w_lst.remove(sock)
w_dit.pop(sock)
原文地址:https://www.cnblogs.com/sophie99/p/9879613.html