Python 协程、IO模型

 

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'))
 

 

posted @ 2018-07-17 21:43  yangzhizong  阅读(344)  评论(0编辑  收藏  举报