【源码学习】python封装IO复用

IO多路复用(multiplexing)

IO多路复用,又称事件驱动,是操作系统提供的一项服务:由操作系统监控一个或多个IO,一旦可读或可写就通知对应的进程。相关的系统调用有大多数系统都支持的select, poll,linux上的epoll, BSD(包括mac os)上的kqueue等。
举个例子,你在等一个快递(等待一个IO事件),必须收到这个快递才能做后续的工作,你的选择可以是反复打电话问快递员到了没(非阻塞忙轮询),或者休息(阻塞)等着被快递员叫醒。显然前者在浪费大家的时间,一般人都会选择后者,即阻塞IO。
单线程阻塞IO只能监控一个流,引入多线程多进程徒增开销。非阻塞忙轮询IO倒是可以监控很多个流,但是效率低。为此,我们找了一个代理(select, poll, epoll或kqueue),同时监控多个流。我们的程序闲时阻塞,有事发生时醒来。epoll,kqueue比select高效。

python封装的系统调用

语言环境 python 2.7.14,相关系统调用在select包里。

import select
dir(select)
# linux下有epoll, poll, select
# mac下有kqueue, select

epoll

# 返回一个epoll对象
select.epoll([sizehint=-1]) # 正整数或-1, 用于优化内部数据结构,并不据此限制事件数量,-1为默认大小
API签名描述
pollpoll([timeout=-1[, maxevents=-1]]) -> [(fd, events), (…)]等待事件发生,timeout为秒数,-1为等待无限长,maxevent返回事件最大数量
registerregister(fd[, eventmask]) -> None为一个文件描述符注册监听事件,如果注册过会引发异常
unregisterunregister(fd) -> None
modifymodify(fd, eventmask) -> None
closeclose() -> None关闭epoll对象,对其的后续操作会引发异常

select

select系统调用会等待,直到指定的IO(一个或多个)可读rlist、可写wlist、或者有其他预定义的事件待处理xlist。如果只有一种情况发生,其他list返回空列表。

select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)

由于select只知道有事件发生了,不知道发生在哪个IO上,故后续还需遍历检查才知道该调哪个回调函数处理。select只有一个函数,epoll是一个对象,ssloop对select进行了封装,使其接口与epoll接口一致。

# ssloop对select系统调用的封装
class SelectLoop(object):

    def __init__(self):
        self._r_list = set()
        self._w_list = set()
        self._x_list = set()

    def poll(self, timeout):
        # 封装后的select系统调用
        # 返回[(fd, events), (...)]
        r, w, x = select.select(self._r_list, self._w_list, self._x_list,
                                timeout)
        # POLL_NULL = 0x00
        results = defaultdict(lambda: POLL_NULL) 
        # POLL_IN = 0x01,POLL_OUT = 0x04,POLL_ERR = 0x08
        for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]:
            for fd in p[0]:
                results[fd] |= p[1]
        return results.items()

    def register(self, fd, mode):
        if mode & POLL_IN:
            self._r_list.add(fd)
        if mode & POLL_OUT:
            self._w_list.add(fd)
        if mode & POLL_ERR:
            self._x_list.add(fd)

    def unregister(self, fd):
        if fd in self._r_list:
            self._r_list.remove(fd)
        if fd in self._w_list:
            self._w_list.remove(fd)
        if fd in self._x_list:
            self._x_list.remove(fd)

    def modify(self, fd, mode):
        self.unregister(fd)
        self.register(fd, mode)

    def close(self):
        # select系统调用只是一个函数,不是对象,不提供close
        pass

kqueue

API签名描述
controlcontrol(changelist, max_events[, timeout=None]) -> eventlistchangelist是kevent的列表,timeout为None时等待时间无穷
closeclose() -> None

官方样例如下

# To start watching a socket for input:
>>> kq = kqueue()
>>> sock = socket()
>>> sock.connect((host, port))
>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_ADD)], 0)

# To wait one second for it to become writeable:
>>> kq.control(None, 1, 1000)

# To stop listening:
>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_DELETE)], 0)

kqueue定义kevent再使用control函数进行注册。ssloop对kqueue进行了封装,使其接口与epoll接口一致。

# ssloop对BSD上kqueue系统调用的封装
class KqueueLoop(object):

    MAX_EVENTS = 1024

    def __init__(self):
        self._kqueue = select.kqueue()
        self._fds = {}

    def _control(self, fd, mode, flags):
        # 对kqueue.control的封装,将kqueue定义的事件转本地定义的事件
        events = []
        if mode & POLL_IN:
            events.append(select.kevent(fd, select.KQ_FILTER_READ, flags))
        if mode & POLL_OUT:
            events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags))
        for e in events:
            self._kqueue.control([e], 0)

    def poll(self, timeout):
        if timeout < 0:
            timeout = None  # kqueue behaviour
        events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout)
        results = defaultdict(lambda: POLL_NULL)
        for e in events:
            fd = e.ident
            if e.filter == select.KQ_FILTER_READ:
                results[fd] |= POLL_IN
            elif e.filter == select.KQ_FILTER_WRITE:
                results[fd] |= POLL_OUT
        return results.items()

    def register(self, fd, mode):
        self._fds[fd] = mode
        self._control(fd, mode, select.KQ_EV_ADD)

    def unregister(self, fd):
        self._control(fd, self._fds[fd], select.KQ_EV_DELETE)
        del self._fds[fd]

    def modify(self, fd, mode):
        self.unregister(fd)
        self.register(fd, mode)

    def close(self):
        self._kqueue.close()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值