1.协程(单线程实现并发)
2.I/0模型
2.1阻塞I/O
2.2非阻塞I/O
知识点一:协程
协程的目的:是想要在单线程下实现并发(并发看起来是同时运行的)
并发=多个任务间切换+保存状态(正常情况都是由操作系统来控制的)
一般情况下都是由操作系统来控制的,现在要实现的就是遇到I/o自己来切换,也叫并发
优点:在应用程序级别速度要远远高于操作系统的切换
缺点:多个任务一旦有一个阻塞没有切,整个线程都会阻塞原地,该线程内的其他任务
都不能执行
一旦引入协程,就需要检测单线程下所有的IO行为,实际遇到IO就切换,
少一个都不行,一旦一个任务阻塞了,整个线程就阻塞了,其他的任务即便是
可以计算,但是也无法运行了
一个程序没有遇到IO也切,反而会降低效率,应该找到一种让程序遇到IO切,才能提高效率
gevent模块:模拟识别IO阻塞
#gevent模块:模拟识别IO阻塞
importgeventfrom gevent import monkey,spawn;monkey.patch_all() #pathch_all()打补丁就是让gevent能识别所有的IO
from threading importcurrent_threadimporttimedefeat():print('%s eat 1'%current_thread().name)#gevent.sleep(2) #默认只能识别自己模块的Io行为
time.sleep(3)print('%s eat 2'%current_thread().name)defplay():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 importspawn,monkey;monkey.patch_all()from socket import *
from threading importThreaddeftalk(conn):whileTrue:try:
data=conn.recv(1024)if len(data) == 0:breakconn.send(data.upper())exceptConnectionResetError:breakconn.close()def server(ip,port,backlog=5):
server=socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(backlog)print('starting...')whileTrue:
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 *
importos
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))whileTrue:
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 *
importtime
server=socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)#所有的IO行为都变为非阻塞模型,设置为False
conn_l=[] #保存的是一堆连接,便于后面与每个客户端通信收发消息
whileTrue:#建立连接循环:
try:print('总连接数[%s]' %len(conn_l))
conn,addr=server.accept()
conn_l.append(conn)except BlockingIOError: #客户建立连接请求,报错信息,捕捉这个异常可以执行下面的代码,即执行其他任务
#print('客户端没有数据过来,可以执行其他任务')
del_l=[] #另外新建一个列表单独存放那些非法数据,方便最后清理这些数据
#建立通信循环:
for conn inconn_l:try:
data=conn.recv(1024) #与accept()建立连接一样,如果没有消息过来,直接
if len(data) == 0: #输入的是空值
del_l.append(conn)continueconn.send(data.upper())exceptBlockingIOError:pass
except ConnectionResetError: #客户端单方面终止连接
del_l.append(conn) #将终止的连接添加到定义的列表里面,最后统一删除这些无效的连接
for conn indel_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'))