1.协程(单线程实现并发)
2.I/0模型
2.1阻塞I/O
2.2非阻塞I/O
知识点一:协程
协程的目的:是想要在单线程下实现并发(并发看起来是同时运行的)
并发=多个任务间切换+保存状态(正常情况都是由操作系统来控制的)
一般情况下都是由操作系统来控制的,现在要实现的就是遇到I/o自己来切换,也叫并发
优点:在应用程序级别速度要远远高于操作系统的切换
缺点:多个任务一旦有一个阻塞没有切,整个线程都会阻塞原地,该线程内的其他任务
都不能执行
一旦引入协程,就需要检测单线程下所有的IO行为,实际遇到IO就切换,
少一个都不行,一旦一个任务阻塞了,整个线程就阻塞了,其他的任务即便是
可以计算,但是也无法运行了
一个程序没有遇到IO也切,反而会降低效率,应该找到一种让程序遇到IO切,才能提高效率
gevent模块:模拟识别IO阻塞
#gevent模块:模拟识别IO阻塞 import gevent from gevent import monkey,spawn;monkey.patch_all() #pathch_all()打补丁就是让gevent能识别所有的IO from threading import current_thread import time def eat(): print('%s eat 1'%current_thread().name) # gevent.sleep(2) #默认只能识别自己模块的Io行为 time.sleep(3) print('%s eat 2'%current_thread().name) def play(): print('%s play 1'%current_thread().name) # gevent.sleep(1) time.sleep(1) print('%s play 2'%current_thread().name) #创建协程对象 g1=spawn(eat,) #spawn(函数名,参数1...) 都是传给函数eat的 g2=spawn(play,) print(current_thread().name) g1.join() #等待g1结束 g2.join() #等待g2结束 '''输出结果: MainThread DummyThread-1 eat 1 #假线程 DummyThread-2 play 1 DummyThread-2 play 2 DummyThread-1 eat 2 ''' ''' 而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了 from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前 或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
gevent应用实例一:利用gevent模块改写socket服务端实现:单线程IO切换自动切换
#服务端 from gevent import spawn,monkey;monkey.patch_all() from socket import * from threading import Thread def talk(conn): while True: try: data=conn.recv(1024) if len(data) == 0:break conn.send(data.upper()) except ConnectionResetError: break conn.close() def server(ip,port,backlog=5): server = socket(AF_INET, SOCK_STREAM) server.bind((ip, port)) server.listen(backlog) print('starting...') while True: conn, addr = server.accept() spawn(talk,conn) #原来造线程的方式: # t = Thread(target=talk, args=(conn,)) # t.start() if __name__ == '__main__': # server('127.0.0.1',8080) g=spawn(server,'127.0.0.1',8080) g.join() ------------------------------------------------------------------------------- #客户端 from socket import * import os client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg='%s say hello' %os.getpid() client.send(msg.encode('utf-8')) data=client.recv(1024) print(data.decode('utf-8'))
知识点二:基于网络的IO模型
可以分为2大类:
第一类:
server.accept()
第二类:
conn.recv()
conn.send()
1.recv(收消息)
wait data:等待客户端产生数据--》客户端OS--》网络--》服务端操作系统缓存
copy data:由本地操作系统缓存中的数据拷贝到应用程序的内存中
2.send(发消息)
copy data
阻塞IO:blocking IO
blocking IO就是在执行的2个阶段(等待数据和拷贝数据两个阶段)都被block了
非阻塞IO:non-blocking IO:
思考:目的就是将将recv()阻塞的这个模型变为非阻塞,
即如果没有数据先执行其它的任务,并且最好在没有数据时客户端能返回一个
没有数据的提示信息
非阻塞IO:的问题
1.CPU占用率高(多数的询问是无用的)
2.for循环列表多的情况下会慢
3.数据得不到及时的处理(因为有时候存在可能刚切换到其他任务,数据就过来了
这样就不能及时处理)
非阻塞IO模型改写socket套接字服务端、客户端:
#服务端 from socket import * import time server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) server.setblocking(False) #所有的IO行为都变为非阻塞模型,设置为False conn_l=[] #保存的是一堆连接,便于后面与每个客户端通信收发消息 while True: #建立连接循环: try: print('总连接数[%s]' % len(conn_l)) conn,addr=server.accept() conn_l.append(conn) except BlockingIOError: #客户建立连接请求,报错信息,捕捉这个异常可以执行下面的代码,即执行其他任务 # print('客户端没有数据过来,可以执行其他任务') del_l=[] #另外新建一个列表单独存放那些非法数据,方便最后清理这些数据 #建立通信循环: for conn in conn_l: try: data=conn.recv(1024) #与accept()建立连接一样,如果没有消息过来,直接 if len(data) == 0: #输入的是空值 del_l.append(conn) continue conn.send(data.upper()) except BlockingIOError: pass except ConnectionResetError: #客户端单方面终止连接 del_l.append(conn) #将终止的连接添加到定义的列表里面,最后统一删除这些无效的连接 for conn in del_l: conn_l.remove(conn)
-----------------------------------------------------------------------------------------------------------------
#客户端:不需要变动
from socket import *
import os
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
msg='%s say hello' %os.getpid()
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print(data.decode('utf-8'))