【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
select 模块一般有两个主要对象 —— select 函数和 Polling 对象。select 一般各平台都会有,而 Polling 是区分平台实现的,比如在 Linux 上他就是 epoll,在 Solaris 上叫做 devpoll。
select 和 epoll 实际都是访问系统调用,功能是等待 I/O 完成。
select
select(rlist, wlist, xlist[, timeout]) 函数接受三个文件描述符的列表作为参数,并将其中就绪的文件描述符返回。如果当前没有就阻塞调用。以 socket 为例,假设有两对 socket 对象 s1 和 s2, 都没有未读取的数据,那么:
lang:python
>>> import select
>>> x = select.select([s1, s2], [], [])
...
就会阻塞。直到彼端的 socket 发送数据:
# 这里是另一端的 s1 和 s2
>>> s1.send(b'hello')
此端的 select 调用就会返回:
>>> x
([s1], [], [])
>>> x[0][0].recv(100)
b'hello'
epoll
Polling Object 是对各平台上此类对象的一个统称,下面皆用 Linux 的实现 epoll 指代。
epoll 的功能和 select 一样,但是算法效率更高,并发处理能力也更大。epoll 还可以支持很多种 eventmask,即触发事件不仅限于 select 的可读、可写、可执行。比如它的触发方式就可以分为 边界触发(edge-triggered) 和 电平触发(level-triggered)。边界触发的条件是文件描述符发生实际状态变化,而电平触发取决于文件描述符当前是否满足条件。如上例中,假如最后调用的是 s1.recv(4) 返回一个 b'hell',即缓冲里还剩余了一个字节没有读。如果这时调用epoll.poll(),那么边界触发就不会生效,而电平触发则会。
这些触发事件是通过二进制掩码来定义的,并作为常量保存在 select 模块里。可以这样查看:
>>> import select
>>> for name in dir(select):
... if name.isupper():
... print(name, bin(getattr(select, name))
...
EPOLLERR 0b1000
EPOLLET 0b10000000000000000000000000000000
EPOLLHUP 0b10000
EPOLLIN 0b1
EPOLLMSG 0b10000000000
EPOLLONESHOT 0b1000000000000000000000000000000
EPOLLOUT 0b100
EPOLLPRI 0b10
EPOLLRDBAND 0b10000000
EPOLLRDNORM 0b1000000
EPOLLWRBAND 0b1000000000
EPOLLWRNORM 0b100000000
EPOLL_CLOEXEC 0b10000000000000000000
PIPE_BUF 0b1000000000000
POLLERR 0b1000
POLLHUP 0b10000
POLLIN 0b1
POLLMSG 0b10000000000
POLLNVAL 0b100000
POLLOUT 0b100
POLLPRI 0b10
POLLRDBAND 0b10000000
POLLRDNORM 0b1000000
POLLWRBAND 0b1000000000
POLLWRNORM 0b100000000
其中需要注意的是,EPOLLET 作为边界触发的一个掩码,是要与其他普通码一起来用的。即 EPOLLET 无法被单独触发,比如想定义文件描述符第一次处于可读状态这个事件,就要把 poll 的 eventmask 参数写成 EPOLLIN|EPOLLET。类似的还有 EPOLONESHOT。例:
>>> sock.fileno()
99
>>> epoll.register(sock, EPOLLIN)
# 此时彼端 sock.send(b'hello')
>>> epoll.poll()
[(99, 1)]
>>> epoll.poll()
[(99, 1)]
这里连续调用两次 poll,都可以不阻塞地得到 sock 的 fileno 和 eventmask,是因为默认的触发方式是 level-triggered。
>>> sock.fileno()
99
>>> epoll.register(sock, EPOLLIN|EPOLLET)
# 此时彼端 sock.send(b'hello')
>>> epoll.poll()
[(99, 1)]
>>> epoll.poll()
...
这里将 eventmask 设置为 edge-triggered 式后,第一次调用 poll() 还可以触发事件,但第二次就阻塞了。
EpollSelector
EpollSelector 是 selectors 模块的一个类。selector 构建于 select 之上,提供更加便于操作和高性能的 IO 复用接口。因为 select 的实现区分平台,所以 selectors 提供了一个 DefaultSelector 名字指向当前平台最佳的 Selector 类。
Selector 对象提供的方法与 Polling Object 类似,主要是 .register(fileobj, events, data=None)、unregister(fileobj)、select(timeout=None)。其中 register 接受一个特别的 data 参数,这是一个与 fileobj 关联的对象,会在 select 调用后与 fileobj 一起放在 SelectorKey 中返回。比如一个回调函数。
来源:oschina
链接:https://my.oschina.net/u/660175/blog/486324