0. 参考地址

基本介绍 https://www.cnblogs.com/yinheyi/p/8127871.html

实验演示 https://www.cnblogs.com/xybaby/p/6406191.html#_label_2

详细讲解 http://aju.space/2017/07/31/Drive-into-python-asyncio-programming-part-1.html

官方文档selecotors https://docs.python.org/3/library/selectors.html

官方文档select https://docs.python.org/3/library/select.html

1. 前言

并发的解决方案中,因为阻塞IO调用的原因,同步模型(串行/多进程/多线程)并不适合大规模高并发.在非阻塞IO调用中,我们可以使用一个线程完成高并发的功能,不过因为非阻塞IO会立即返回,如何判断IO准备就绪以及就绪之后如何处理就变成了关键,所以我们需要附带额外的处理.

不论使用哪一种额外处理方式,核心都是为了获知IO准备就绪且执行对应的操作,额外处理方式之一就是回调+事件循环.

OS已经为我们提供了select/poll/epoll/kqueue等多种底层操作系统接口用以处理IO准备就绪的通知(即通过OS提供的接口可以方便的编写事件循环).而程序还需要完成:如何在IO准备就绪的时候执行预定的操作.

selecotrs模块,总代码611行,其中有5个类是同一个级别,只是根据OS的类型而有所不同.模块中还包含大量的注释,所以核心代码数量就在100行左右.selectors模块为我们提供了异步编程中的回调模型(后面还会写异步编程中的协程模型),所以我觉得对此模块的研究是很有必要的.

2. 核心类

selectors模块中的核心类如下:


BaseSelector:是一个抽象基类,定义了核心子类的函数接口.BaseSelector类定义的核心接口如下:

@abstractmethod
register(self, fileobj, events, data=None) # 提供文件对象的注册 @abstractmethod
unregister(self, fileobj) # 注销已注册的文件对象 @abstractmethod
select(self, timeout=None) # 向OS查询准备就绪的文件对象

其中,前两个函数封装了文件对象,并提供了data变量用于保存附加数据,这就提供了回调的环境.第三个函数select是对OS底层select/poll/epoll接口的封装,用以提供一个统一的对外接口.

_BaseSelectorImpl:是一个实现了registerunregister的基类,注意,此基类并没有实现select函数,因为select函数在不同OS上使用的底层接口不同,所以应该在对应的子类中定义

SelectSelector:使用windows时的接口

EpollSelector:使用linux时的接口(其他3个类相似,只是应用于不同的OS)

DefaultSelector:此为类别名,selectors模块会根据所在操作系统的类型,选择最优的接口

如下只对selectselector类的核心代码进行分析,其他对应类的代码逻辑基本一致.

3. SelectSelector核心函数代码分析

有名元祖selectorkey

SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])

此对象是一个有名元祖,可以认为是对文件对象fileobj,对应的描述符值fd,对应的事件events,附带的数据data这几个属性的封装.此对象是核心操作对象,关联了需要监控的文件对象,关联了需要OS关注的事件,保存了附带数据(其实这里就放的回调函数)

3.1 注册

def __init__(self):
super().__init__()
self._readers = set() # 使用集合处理唯一性
self._writers = set()

首先,构造函数中定义了_readers_writers变量用于保存需要监听的文件对象的文件描述符值,并使用集合特性来处理唯一性.

def register(self, fileobj, events, data=None):
key = super().register(fileobj, events, data)
if events & EVENT_READ:
self._readers.add(key.fd)
if events & EVENT_WRITE:
self._writers.add(key.fd)
return key

一般我们使用register作为第一个操作的函数,代表着你需要监听的文件对象,以及,当它发生你关注的事件时,你要如何处理.

此函数有3个参数,分别是文件对象,监听事件(可读为1,可写为2),附带数据.

fileobj文件对象是类文件对象,与平台强相关,在windows上只能是socket,在linux上可以是任何linux支持的文件对象.

events是一个int类型的值,就是EVENT_ERADEVENT_WRITE

data是附带数据,我们可以把回调函数放在这里

此函数返回的key就是一个selectorkey有名元祖

register函数将用户监听的文件对象和事件注册到有名元祖中,并加入监听集合_readers_writers

3.2 注销

def unregister(self, fileobj):
key = super().unregister(fileobj)
self._readers.discard(key.fd)
self._writers.discard(key.fd)
return key

当我们不需要监听某一个文件对象时,使用unregister注销它.这会使得它从_readers_writers中被弹出.

3.3 查询

def select(self, timeout=None):
timeout = None if timeout is None else max(timeout, 0)
ready = []
try:
r, w, _ = self._select(self._readers, self._writers, [], timeout)
except InterruptedError:
return ready
r = set(r)
w = set(w)
for fd in r | w:
events = 0
if fd in r:
events |= EVENT_READ
if fd in w:
events |= EVENT_WRITE key = self._key_from_fd(fd)
if key:
ready.append((key, events & key.events))
return ready

这段代码描述了用户向OS发起的查询逻辑.select函数的timeout参数默认是None,这意味着默认情况下,如果没有任何一个就绪事件的发生,select调用会被永远阻塞.

select函数调用底层select/poll/epoll接口,此函数在SelectSelector类和EpollSelector类中的定义有所区别,会根据OS的类型调用对应接口,windowslinux实际调用的底层接口对比如下:

用户统一调用高层select函数,此函数实际调用的接口为:

# windows下使用select(SelectSelector类)
r, w, _ = self._select(self._readers, self._writers, [], timeout) # linux下使用epoll(EpollSelector类)
fd_event_list = self._epoll.poll(timeout, max_ev)

函数使用ready变量保存准备就绪的元祖(key, events)

windows中,一旦底层select接口返回,会得到3个列表,前两个表示可读和可写的文件对象列表,并使用集合处理为唯一性.准备就绪的元祖对象会加入ready列表中返回.如果定义了timeout不为None,且发生了超时,会返回一个空列表.

4. 别名

# Choose the best implementation, roughly:
# epoll|kqueue|devpoll > poll > select.
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
if 'KqueueSelector' in globals():
DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
else:
DefaultSelector = SelectSelector

selectors模块定义了一个别名DefaultSelector用于根据OS类型自动指向最优的接口类.

5. 总结

1 操作系统提供的select/poll/epoll接口可以用于编写事件循环,而selectors模块封装了select模块,select模块是一个低级别的模块,封装了select/poll/epoll/kqueue等接口.

2 selectors模块中定义了有名元祖selectorkey,此对象封装了文件对象/描述符值/事件/附带数据,selectorkey为我们提供了回调的环境

3 使用selectors模块可以实现使用回调模型来完成高并发的方案.

4 (非常重要)异步回调模型,大部分事件和精力都是对回调函数的设计.回调模型使得每一个涉及IO操作的地方都需要单独分割出来作为函数,这会分割代码导致可读性下降和维护难度的上升.

5 回调函数之间的通信很困难,需要通过层层函数传递.

6 回调模型很难理解

6. 代码报错问题

1. 文件描述符数量

Traceback (most recent call last):
File "F:/projects/hello/hello.py", line 119, in <module>
loop()
File "F:/projects/hello/hello.py", line 102, in loop
events = selector.select()
File "F:\projects\hello\selectors.py", line 323, in select
r, w, _ = self._select(self._readers, self._writers, [], timeout)
File "F:\projects\hello\selectors.py", line 314, in _select
r, w, x = select.select(r, w, w, timeout)
ValueError: too many file descriptors in select()

windows上,底层使用的是select接口,可以支持的文件描述符数量理论说是1024,实际测试描述符必须小于512(我的电脑是win10 64bit)

linux上使用的是epoll,可以支持大于1024的文件描述符数量,不过测试发现在达到4000左右的时候也会报错。

stack overflow解释1:https://stackoverflow.com/questions/31321127/too-many-file-descriptors-in-select-python-in-windows

stack overflow解释2:

https://stackoverflow.com/questions/47675410/python-asyncio-aiohttp-valueerror-too-many-file-descriptors-in-select-on-win

2. 监听列表是否可以为空

Traceback (most recent call last):
File "F:/projects/hello/world.py", line 407, in <module>
loop()
File "F:/projects/hello/world.py", line 378, in loop
events = selector.select()
File "F:\projects\hello\selectors.py", line 323, in select
r, w, _ = self._select(self._readers, self._writers, [], timeout)
File "F:\projects\hello\selectors.py", line 314, in _select
r, w, x = select.select(r, w, w, timeout)
OSError: [WinError 10022] 提供了一个无效的参数。

windows上,监听的文件对象列表不可以为空:

7. 关系图

python异步编程--回调模型(selectors模块)的更多相关文章

  1. (转)python异步编程--回调模型(selectors模块)

    原文:https://www.cnblogs.com/zzzlw/p/9384308.html#top 目录 0. 参考地址 1. 前言 2. 核心类 3. SelectSelector核心函数代码分 ...

  2. 深入理解 Python 异步编程(上)

    http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...

  3. 深入理解Python异步编程(上)

    本文代码整理自:深入理解Python异步编程(上) 参考:A Web Crawler With asyncio Coroutines 一.同步阻塞方式 import socket def blocki ...

  4. python 异步编程

    Python 3.5 协程究竟是个啥 Yushneng · Mar 10th, 2016 原文链接 : How the heck does async/await work in Python 3.5 ...

  5. 这篇文章讲得精彩-深入理解 Python 异步编程(上)!

    可惜,二和三现在还没有出来~ ~~~~~~~~~~~~~~~~~~~~~~~~~ http://python.jobbole.com/88291/ ~~~~~~~~~~~~~~~~~~~~~~~~~~ ...

  6. Python 异步编程笔记:asyncio

    个人笔记,不保证正确. 虽然说看到很多人不看好 asyncio,但是这个东西还是必须学的.. 基于协程的异步,在很多语言中都有,学会了 Python 的,就一通百通. 一.生成器 generator ...

  7. python 并发编程 io模型 目录

    python 并发编程 IO模型介绍 python 并发编程 socket 服务端 客户端 阻塞io行为 python 并发编程 阻塞IO模型 python 并发编程 非阻塞IO模型 python 并 ...

  8. 最新Python异步编程详解

    我们都知道对于I/O相关的程序来说,异步编程可以大幅度的提高系统的吞吐量,因为在某个I/O操作的读写过程中,系统可以先去处理其它的操作(通常是其它的I/O操作),那么Python中是如何实现异步编程的 ...

  9. Python并发编程-事件驱动模型

     一.事件驱动模型介绍                                                                                         ...

随机推荐

  1. git-如何不写注释能自动带上修改文件信息

    背景:每次提交git,都要写注释,有些情况注释不太好写,或者根本没有必要写,这时可以通过自动加注释方法,比如可以追加修改了哪些文件 解决:通过shell脚本,在脚本里面写git命令,add commi ...

  2. 134. Gas Station加油站

    [抄题]: There are N gas stations along a circular route, where the amount of gas at station i is gas[i ...

  3. 364. Nested List Weight Sum II 大小反向的括号加权求和

    [抄题]: Given a nested list of integers, return the sum of all integers in the list weighted by their ...

  4. 利用travis自动化构建与部署(文档项目)

    背景 保持网站上文档的最新性有比较重要的意义, travis ci 提供了免费的解决方案,本文基于 latex 构建+ aliyun oss 部署对此作了尝试. 项目链接为 https://travi ...

  5. [leetcode]156.Binary Tree Upside Down颠倒二叉树

    Given a binary tree where all the right nodes are either leaf nodes with a sibling (a left node that ...

  6. 5. Redis持久化

    5. Redis持久化5.1 RDB5.1.1 触发机制5.1.2 流程说明5.1.3 RDB文件的处理5.1.4 RDB的优缺点5.2 AOF5.2.1 使用AOF5.2.2 命令写入5.2.3 文 ...

  7. BootStrap初始

    序言 什么是Bootstrap Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的. Bootstrap是 ...

  8. 如何查询linux下BIOS信息

    一般可以使用dmidecode命令(还有biosdecode命令可参考),背景知识如下: SMBIOS (System Management BIOS)是主板或系统制造者以标准格式显示产品管理信息所需 ...

  9. 如何通过RNA-Seq了解转录本的结构

    [转载]如何通过RNA-Seq了解转录本的结构 已有 1942 次阅读 2014-12-26 15:22 |个人分类:转录组测序|系统分类:科研笔记|关键词:RNA-Seq,转录组测序,转录本结构|  ...

  10. Win 10 安装手机驱动

    直接上图,看图操作即可.