python异步函数 python异步执行命令( 六 )


目前,Linux上比较火的I/O多路复用API要算epoll了 。Tornado,就是通过调用C语言封装的epoll库,成功解决了C10K问题(当然还有Pypy的功劳) 。
在Linux里查文档,可以看到epoll只有三类函数,调用起来比较方便易懂 。
创建epoll对象,并返回其对应的文件描述符(file descriptor) 。
int epoll_create(int size);
int epoll_create1(int flags);
控制监听事件 。第一个参数epfd就对应于前面命令创建的epoll对象的文件描述符;第二个参数表示该命令要执行的动作:监听事件的新增、修改或者删除;第三个参数,是要监听的文件对应的描述符;第四个 , 代表要监听的事件 。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
等候 。这是一个阻塞函数,调用者会等候内核通知所注册的事件被触发 。
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
在Python的select库里:
select.epoll()对应于第一类创建函数;
epoll.register(),epoll.unregister() , epoll.modify()均是对控制函数epoll_ctl的封装;
epoll.poll()则是对等候函数epoll_wait的封装 。
Python里epoll相关API的最大问题应该是在epoll.poll() 。相比于其所封装的epoll_wait,用户无法手动指定要等候的事件 , 也就是后者的第二个参数struct epoll_event *events 。没法实现精确控制 。因此只能使用替代方案:select.select()函数 。
根据Python官方文档,select.select(rlist, wlist, xlist[, timeout])是对Unix系统中select函数的直接调用 , 与C语言API的传参很接近 。前三个参数都是列表,其中的元素都是要注册到内核的文件描述符 。如果想用自定义类,就要确保实现了fileno()方法 。
其分别对应于:
rlist: 等候直到可读
wlist: 等候直到可写
xlist: 等候直到异常 。这个异常的定义,要查看系统文档 。
select.select(),类似于epoll.poll(),先注册文件和事件,然后保持等候内核通知,是阻塞函数 。
实际应用
Psycopg2库支持对异步和协程,但和一般情况下的用法略有区别 。普通数据库连接支持不同线程中的不同游标并发查询;而异步连接则不支持不同游标的同时查询 。所以异步连接的不同游标之间必须使用I/O复用方法来协调调度 。
所以,我的大致实现思路是这样的:首先并发执行大量协程,从任务队列中提取任务,再向连接池请求连接 , 创建游标,然后执行命令,并返回结果 。在获取游标和接受查询结果之前,均要阻塞等候内核通知连接可用 。
其中,连接池返回连接时,会根据引用连接的协程数量,返回负载最轻的连接 。这也是自己定义AsyncConnectionPool类的目的 。
我的代码位于:bottle-blog/dbservice.py
存在问题
当然了 , 这个流程目前还一些问题 。
首先就是每次轮询拿到任务之后,都会走这么一个流程 。
获取连接 -- 新建游标 -- 执行任务 -- 关闭游标 -- 取消连接引用
本来,最好的情况应该是:在轮询之前 , 就建好游标;在轮询时,直接等候内核通知,执行相应任务 。这样可以减少轮询时的任务量 。但是如果协程提前对应好连接 , 那就不能保证在获取任务时,保持各连接负载均衡了 。
所以这一块 , 还有工作要做 。
还有就是epoll没能用上,有些遗憾 。
以后打算写点C语言的内容 , 或者用Python/C API,或者用Ctypes包装共享库,来实现epoll的调用 。

推荐阅读