select()
调用要求您创建三个位掩码来标记您想要监视的套接字和文件描述符,以进行读取、写入和错误检查,然后操作系统标记哪些实际上已经发生了某种活动;poll()
要求您创建一个描述符 ID 列表,操作系统标记每个描述符 ID 的发生事件类型。
select()
方法相当笨重且效率低下。
一个进程通常可以使用超过一千个潜在的文件描述符。如果一个长时间运行的进程只打开了几个描述符,但至少有一个已分配了高编号,则传递给select()
的位掩码必须足够大以容纳最高描述符——因此整个范围的数百位将被取消设置,操作系统必须在每次select()
调用时循环遍历这些未设置的位才能发现它们未设置。
一旦select()
返回,调用者必须循环遍历所有三个位掩码以确定发生了什么事件。在非常多的典型应用程序中,只有一个或两个文件描述符会在任何给定时刻获得新的流量,然而必须读取所有三个位掩码才能发现是哪些描述符。
由于操作系统通过重写位掩码来向您发出有关活动的信号,它们已经被破坏,并且不再标记为您要监听的文件描述符列表。您可以从内存中的其他列表重新构建整个位掩码,或者您可以保留每个位掩码的副本并在每个select()
调用后将数据块memcpy()
覆盖到已破坏的位掩码上。
因此,poll()
方法效果更好,因为您可以继续重复使用相同的数据结构。
事实上,poll()
在现代Linux内核中甚至还启发了另一种机制:epoll()
,它进一步改进了机制,以允许更大规模的跨越,因为今天的服务器通常希望同时处理数万个连接。这是一个关于该努力的良好介绍:
http://scotdoyle.com/python-epoll-howto.html
虽然这个链接展示了一些漂亮的图表,显示了epoll()
的好处(您会注意到select()
已经被认为是非常低效和过时的,甚至在这些图表上都没有一行!):
http://lse.sourceforge.net/epoll/index.html
更新: 这里有另一个 Stack Overflow 的问题,其答案更详细地说明了 select/poll 和 epoll 在 Twisted 中的区别:
我认为 这个 回答了你的问题:
来自 Richard Stevens (rstevens@noao.edu) 的回答:
基本区别在于 select() 的fd_set 是一个位掩码,因此有一些固定大小。当内核被编译时,允许应用程序定义FD_SETSIZE为任意值(如系统头文件中的注释所示),从而不限制内核的大小,但这需要更多的工作。4.4BSD的内核和Solaris库函数都有此限制。但是我看到BSD/OS 2.1现在已经编写以避免此限制,因此它是可行的,只是一个小的编程问题。:-)有人应该在Solaris上报告这个错误,并查看它是否被修复。
然而,对于 poll(),用户必须分配一个pollfd结构的数组,并传递此数组中条目的数量,因此没有根本性的限制。正如Casper所指出的那样,拥有poll()的系统比选择少,因此后者更具可移植性。另外,对于原始实现(SVR3),您无法将描述符设置为-1,以告诉内核忽略pollfd结构中的条目,这使得从数组中删除条目变得困难。 SVR4解决了这个问题。就我个人而言,我总是使用select(),很少使用poll(),因为我还要将我的代码移植到BSD环境中。有人可以编写一个使用select()的poll()实现来适应这些环境,但我从未见过这样的实现。 select()和poll()都被POSIX 1003.1g标准化。
poll()
命令现在(2017年)已经支持所有现代操作系统,包括BSD。事实上,一些人认为select()
应该被弃用。除了不同的观点之外,在现代系统中,poll()
的可移植性问题已经不再是一个问题。此外,epoll()
也已经被开发出来(你可以阅读手册页),并且继续在流行度上升。select()
,虽然这并没有明确的错误。 poll()
及其更现代的演进epoll()
提供与select()
相同的功能(甚至更多),但不会受到其中的限制select
或者poll
的消息。 - Christopher Schultz两者都比较慢,而且大部分功能相同,但在大小和某些特性上有所不同!
当你编写迭代器时,每次都需要复制select
集合!而poll
已经解决了这种问题,使代码更加优美。另一个区别是poll
可以默认处理多于1024个文件描述符(FDs)。poll
可以处理不同的事件,使程序更易读,而不是使用许多变量来处理此类工作。由于有很多检查,poll
和select
中的操作都是线性和缓慢的。
struct pollfd
数组要高效得多,因为你需要间隙或者非间隙的维护将会比fd_set操作昂贵得多)。 - Petr Skocik