我指的是POSIX标准的select和poll系统C API调用.
该select()
调用有创建三个位掩码来标记你想观看的读,写哪些套接字和文件描述符,并且错误,然后哪些其实都有着某种活动的操作系统的标志; poll()
有您创建的描述符ID的列表,并且操作系统标志着他们每个人的那种发生的事件.
该select()
方法相当笨重且效率低下.
通常有超过一千个潜在的文件描述符可供进程使用.如果一个长时间运行的进程只打开了一些描述符,但至少有一个描述符被赋予了一个高数字,那么传递给select()
它的位掩码必须足够大以容纳那个最高的描述符 - 所以整个数百位的范围将是未设置操作系统必须在每次select()
调用时循环以发现它们未被设置.
一旦select()
返回,调用者必须遍历所有三个位掩码以确定发生了什么事件.在很多典型的应用程序中,只有一个或两个文件描述符在任何给定时刻都会获得新流量,但是必须一直读取所有三个位掩码以发现那些描述符.
由于操作系统通过重写位掩码向您发出有关活动的信号,因此它们会被破坏,并且不再标记您要侦听的文件描述符列表.您要么必须从保留在内存中的其他列表重建整个位掩码,要么必须memcpy()
在每次select()
调用后在每个位掩码的顶部保留每个位掩码和数据块的副本.
所以这种poll()
方法效果更好,因为你可以继续使用相同的数据结构.
实际上,poll()
它启发了现代Linux内核中的另一种机制:epoll()
它进一步改进了机制,允许可扩展性又一次飞跃,因为今天的服务器通常希望同时处理数万个连接.这是对这项工作的一个很好的介绍:
http://scotdoyle.com/python-epoll-howto.html
虽然这个链接有一些很好的图表显示了它们的好处epoll()
(你会注意到select()
这一点被认为是如此低效和过时,甚至在这些图上都没有得到一条线!):
http://lse.sourceforge.net/epoll/index.html
更新:这是另一个Stack Overflow问题,其答案提供了有关差异的更多细节:
Twisted中的select/poll与epoll反应器的注意事项
我认为这回答了你的问题:
来自Richard Stevens(rstevens@noao.edu):
基本的区别是select()的fd_set是一个位掩码,因此有一些固定的大小.内核编译时内核可能不会限制此大小,允许应用程序将FD_SETSIZE定义为它想要的任何内容(正如系统头中的注释所暗示的那样),但需要更多的工作.4.4BSD的内核和Solaris库函数都有此限制.但是我看到BSD/OS 2.1现在已被编码以避免这个限制,因此它是可行的,只是编程的一小部分.:-)有人应该就此提交Solaris错误报告,看看它是否得到修复.
但是,使用poll(),用户必须分配一个pollfd结构数组,并传递此数组中的条目数,因此没有基本限制.正如Casper所说,poll()的系统少于select,因此后者更具可移植性.此外,对于原始实现(SVR3),您无法将描述符设置为-1以告知内核忽略pollfd结构中的条目,这使得很难从数组中删除条目; SVR4解决了这个问题.就个人而言,我总是使用select()而很少使用poll(),因为我也将代码移植到BSD环境中.对于这些环境,有人可以编写使用select()的poll()实现,但我从未见过.POSIX 1003.1g正在标准化select()和poll().
上面提到的电子邮件至少和2001年一样古老; 该poll()
命令现在(2017)支持所有现代操作系统 - 包括BSD.事实上,有些人认为select()
应该弃用.除了意见之外,poll()
现代系统不再担心可移植性问题.此外,epoll()
自那时起开发(您可以阅读手册页),并继续普及.
对于现代开发,您可能不想使用它select()
,尽管它没有明显的错误. poll()
,它是更现代的进化epoll()
,提供相同的功能(和更多),select()
而不受其中的限制.