博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第十五节 Python基础之IO操作

Posted on 2017-03-13 16:50  Jasonhy  阅读(114)  评论(0)    收藏  举报

   之前我们说,在遇到IO操作的时候,协程就会自动切换,那么问题来了,切换之后,什么时候切回去呢?还就是怎么去确定IO操作结束了呢?

   我们先来了解一下什么是事件驱动模型,和为什么需要事件驱动模型?事件驱动,顾名思义就是通过事件来驱动程序的运行,它由事件源,事件对象和事件监听器三元素构成,能完成监听器监听事件源,事件源发送事件,监听器收到事件后调用响应函数的动作。它的出现就是解耦事件发送者和接收者之间的联系,事件可动态的增加减少接收者,当业务逻辑变得复杂的时候,它的优势就越能体现出来。

   事件驱动主要包含元素和操作函数:

    ①元素:事件源,事件监听器,事件对象

    ②操作函数:监听动作,发送事件,调用监听器响应函数

   用户根据实际业、业务逻辑定义:

    ①事件源 EventSources

    ②监听器 Listener

    ③事件管理者 EventManager

   案例:python下实现按事件驱动

# 事件管理器
from
queue import Queue,Empty from threading import * class EventManager: ''' 定义一个驱动管理类 ''' def __init__(self): '''初始化事件管理器''' # 事件对象列表 self.__eventQueue = Queue() # 事件管理器开关 self.__active = False # 事件处理线程 self.__thread = Thread(target=self.__Run) # __handlers是一个字典,用来保存对应事件的响应函数 # 其中每个键对应的值是一个列表,列表中保存了对事件监听的响应函数,一对多的关系 self.__handlers = {} def __Run(self): '''引擎运行''' while self.__active == True: try: # 获取事件阻塞时间设为1秒 event = self.__eventQueue.get(block=True,timeout=1) self.__EventProcess(event) except Empty: pass def __EventProcess(self,event): '''处理事件''' # 检查是否存在对象对该事件进行监听的处理函数 if event.type_ in self.__handlers: # 若存在,则按照顺序将事件传递给处理函数执行 for handler in self.__handlers[event.type_]: handler(event) def Start(self): '''启动''' # 将事件管理器设置为启动 self.__active = True # 启动事件处理线程 self.__thread.start() def Stop(self): '''停止''' # 将事件管理器设为停止 self.__active = False # 等待事件处理线程退出 self.__thread.join() def AddEventListener(self,type_,handler): '''绑定事件监听处理函数''' # 尝试获取该事件类型对应的处理函数列表,若无则创建 try: handlerList = self.__handlers[type_] except KeyError: handlerList = [] self.__handlers[type_] = handlerList # 若要注册的处理器不在该事件的处理器列表中,则注册该事件 if handler not in handlerList: handlerList.append(handler) def RemoveEventListener(self,type_,handler): '''移除监听器的处理函数''' pass def SendEvent(self,event): '''发送事件,向事件队列中存入事件''' self.__eventQueue.put(event) class Event: '''定义一个事件对象''' def __init__(self,type_=None): # 事件类型 self.type_ = type_ # 保存事件数据 self.dict = {}
# 事件驱动测试
from
threading import * from eventManager import * # 事件名称 新文章 EVENT_ARTICAL = "Event_artical" # 事件源 公众号 class PublicAccounts: def __init__(self,event_manager): self.__eventManager = event_manager def WriteNewArtical(self): # 事件处理对象 写了写的文章 event = Event(EVENT_ARTICAL) event.dict['artical'] = u'如何写出更优雅的代码' # 发送事件 self.__eventManager.SendEvent(event) print(u"公众号发送了新的文章") class Listener: '''监听器 订阅者''' def __init__(self,username): self.__username = username def ReadArtical(self,event): ''' 监听器 处理函数 :param event: :return: ''' print(u'%s 收到新文章' % self.__username) print(u'正在阅读新文章内容:%s' % event.dict["artical"]) '''测试函数''' def test(): listener1 = Listener("大傻") listener2 = Listener("B哥") event_manager = EventManager() # 绑定事件和监听器响应函数(新文章) event_manager.AddEventListener(EVENT_ARTICAL, listener1.ReadArtical) event_manager.AddEventListener(EVENT_ARTICAL, listener2.ReadArtical) event_manager.Start() publicAcc = PublicAccounts(event_manager) timer = Timer(2, publicAcc.WriteNewArtical) timer.start() if __name__ == '__main__': test()

 

    IO多路复用:简单的说,就是多个客户端连接,比如socketserver,单线程下实现并发效果,我们就叫做多路复用。

    进程的切换: 为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起某个进程的执行,这种行为就叫做进程切换,这种切换是有操作系统完成。因此可以说任何进程都是在操作系统内核的支持下运行,是与内核紧密相关的。从一个进程切换到另一个进程,经过的步骤:

    ①保存处理机上下文,包括程序计数器和其他寄存器

    ②更新PCB信息

    ③把进程的相应PCB移入相应的队列,如就绪,在某事件阻塞等队列

    ④选择另一个进程执行,并更新PCB

    ⑤更新内存管理的数据结构

    ⑥恢复处理机上下文

   从这些步骤,我们发现如果不断的这样切换下去,那多浪费资源啊

   进程阻塞: 正在执行的进程,由于期待的某些事情未发生,如请求系统资源失败,等待某种操作的完成,新数据尚未到达或者无新的工作做等,则由系统自动执行阻塞原语(Block),使得自己的状态变为阻塞状态。可见进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程才可能进入阻塞状态,是不占用CPU资源。

   文件描述符: 用于表述指向文件引用的抽象化概念,是一个索引值,指向内核为每一个所维护的该进程打开文件的记录表,当程序打开一个现有文件或者创建一个文件时,内核向进程返回一个文件描述符。

   缓存IO: 又称作标准IO,大多数系统的默认IO操作都是缓存IO,在Linux的缓存机制中,操作系统会将IO的数据缓存到文件系统的页缓存中,也就是说数据先拷贝操作系统内核的缓冲区中,然后才会从操作系统内核缓冲区拷贝到应用程序的地址空间,用户空间没办法直接访问内核空间。

   IO模型: 

      ①阻塞IO:只发一次系统调用,知道内核空间返回结果之后,这种阻塞才解除

    ②非阻塞IO:发出多次系统调用,轮询内核空间数据准备好了没,如果没有准备内核空间就会返回error,然后接着再次发送read操作,它的缺点就是多次的进行系统调用,还有就是当接收到数据估计会产生没有及时处理的问题

    ③IO多路复用:单线程下实行并发,它的基本原理就是select/epoll这个function会不断的轮询所负责的socket,当某个socket有数据到达时,就通知用户进程。当用户进程调用了select,那么整个进行将被block,而同时,内核会监视所有的select负责的socket,当任何一个socket中的数据准备好了,select就会返回,这个时候用户进程再调用read操作,将数据拷贝到用户进程。

    ④异步IO:用户进程发起read操作之后,立刻就可以去做其他事了,另一方面,内核收到read之后,首先它会立刻返回,所以不会对用户进程产生任何的block,然后内核等待数据准备完成,然后将数据拷贝到用户内存,当一切都完成之后,内核会给用户发送一个signal,告诉它read操作完成了

   IO多路复用select,poll,epoll之间的区别:

    ①select通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符就被内核修改标志位,使得进程可以获得这些文件描述符,从而进行后续的读写操作

    ②poll和select没有多大的区别,只是它没有最大文件描述符数量的限制,一般不用

    ③没有最大文件描述法数量限制,但是windows下不支持;比如100个连接,有两个活跃,epoll会告诉用户这两个活跃,直接取就可以了,但是select是需要循环才取出

   IO多路复用的触发方式:

    ①水平触发:如果文件描述符已经准备就绪就可以执行非阻塞IO操作了,此时会触发通知,允许任意时刻检测IO的状态,没有必要每次描述符准备就绪后尽可能多执行IO,select和poll就是水平触发

     ②边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知,在收到一个IO事件通知后要尽可能多的执行IO操作,因此如果一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的文件描述符,信号驱动式IO就是边缘触发。epoll既可以采用水平触发,也可以采用边缘触发。

   案例:IO多路实现并发聊天

# server 端
from
socket import * import select s_sk = socket(AF_INET,SOCK_STREAM) s_sk.bind(("127.0.0.1",8080)) s_sk.listen(5) inputs = [s_sk,] while True: r,w,e=select.select(inputs,[],[],5) print(len(r)) for obj in r: if obj == s_sk: conn,addr = s_sk.accept() print(conn) inputs.append(conn) else: data = obj.recv(1024) print(data.decode("utf-8")) inp = input("回答%s号客户>>>"%inputs.index(obj)) obj.sendall(inp.encode("utf-8")) print(r)

# client 端
import
socket sk=socket.socket() sk.connect(('127.0.0.1',8080)) while True: inp=input(">>>>") sk.sendall(inp.encode("utf-8")) data=sk.recv(1024) print(data.decode("utf-8"))

 

 

    案例:selectors模块

# server 端
import
selectors import socket # 创建DefaultSelector对象 sel = selectors.DefaultSelector() def accept(sock,mask): conn,addr = sock.accept() print("accept",conn,"from",addr) conn.setblocking(False) sel.register(conn,selectors.EVENT_READ,read) def read(conn,mask): data = conn.recv(1000) if data: print("echoing",repr(data),"to",conn) conn.send(data) else: print("closing",conn) sel.unregister(conn) conn.close() sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.bind(("127.0.0.1",8080)) sock.listen(100) # 设置非阻塞 sock.setblocking(False) # 注册 将sock和accept进程绑定 sel.register(sock,selectors.EVENT_READ,accept) while True: # 将所有内容封装到event里面,是一个列表 events = sel.select() print("events >>>",events) for key,mask in events: print("key >>>",key) print("mask >>>",mask) # callback这里就是accept函数 callback = key.data print("callback >>>",callback) # key.fileobj 就是服务端socket对象 callback(key.fileobj,mask) # 实际执行的就是accept

# client 端
import
socket sk = socket.socket() sk.connect(("127.0.0.1",8080)) while True: inp = input(">>>>>:") sk.send(inp.encode("utf-8")) data = sk.recv(1024) print("reciever:",data.decode("utf-8"))