轮询和选择(select)有什么区别?

200

我正在提到POSIX标准的selectpoll系统C API调用。

3个回答

258

select() 调用要求您创建三个位掩码来标记您想要监视的套接字和文件描述符,以进行读取、写入和错误检查,然后操作系统标记哪些实际上已经发生了某种活动;poll() 要求您创建一个描述符 ID 列表,操作系统标记每个描述符 ID 的发生事件类型。

select() 方法相当笨重且效率低下。

  1. 一个进程通常可以使用超过一千个潜在的文件描述符。如果一个长时间运行的进程只打开了几个描述符,但至少有一个已分配了高编号,则传递给select()的位掩码必须足够大以容纳最高描述符——因此整个范围的数百位将被取消设置,操作系统必须在每次select()调用时循环遍历这些未设置的位才能发现它们未设置。

  2. 一旦select()返回,调用者必须循环遍历所有三个位掩码以确定发生了什么事件。在非常多的典型应用程序中,只有一个或两个文件描述符会在任何给定时刻获得新的流量,然而必须读取所有三个位掩码才能发现是哪些描述符。

  3. 由于操作系统通过重写位掩码来向您发出有关活动的信号,它们已经被破坏,并且不再标记为您要监听的文件描述符列表。您可以从内存中的其他列表重新构建整个位掩码,或者您可以保留每个位掩码的副本并在每个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 中的区别:

在 Twisted 中使用 select/poll 与 epoll 反应器的注意事项


1
另外加1分,因为提供了在Python中使用epoll的示例链接 - 看起来那里有一些有趣的例子,我得试试它们... - Allen George
这个答案让人觉得epoll总是更可取。 - user3467349
1
你让它看起来好像位扫描是瓶颈一样,但实际上并不是。在Linux上扫描一个1024位的数组(1024是FD_SETSIZE)大约需要20纳秒。系统调用需要数百甚至数千个纳秒。稀疏间隙可以被相当高效地跳过(比使用struct pollfd数组要高效得多,因为你需要间隙或者非间隙的维护将会比fd_set操作昂贵得多)。 - Petr Skocik
根据我找到的基准测试,它们在处理大型fd集时似乎同样高效/低效:https://monkey.org/~provos/libevent/libevent-benchmark2.jpg,在高fd计数时,poll仅略胜一筹,可能是因为select比poll更频繁地修改其参数集。 - Petr Skocik

114

我认为 这个 回答了你的问题:

来自 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标准化。

更新于2017年10月:

上述提到的电子邮件至少有2001年的历史; poll()命令现在(2017年)已经支持所有现代操作系统,包括BSD。事实上,一些人认为select()应该被弃用。除了不同的观点之外,在现代系统中,poll()的可移植性问题已经不再是一个问题。此外,epoll()也已经被开发出来(你可以阅读手册页),并且继续在流行度上升。
对于现代开发而言,您可能不想使用select(),虽然这并没有明确的错误。 poll()及其更现代的演进epoll()提供与select()相同的功能(甚至更多),但不会受到其中的限制

17
史蒂文的回答是什么时候写的?有关poll()在BSD上不可用的评论是否仍然适用?MacOS X(部分基于BSD)具有poll()功能,并且POSIX标准(POSIX 2008)要求使用它。 - Jonathan Leffler
15
Rich Stevens于1999年9月去世,因此答案必须比那个时间更早。他提到了在BSD/OS 2.1中看到的新变化,该版本于1996年1月发布,因此可能是在那个时候左右。 - alanc
2
我简直不敢相信。5年前发布的回答,我偶然间发现并在浏览器中保持打开状态。紧接着的第二天,作者对回答进行了编辑和改进。SO使用AJAX/websocket通知我页面更新。这就是为什么SO如此出色。 - Steven Lu
12
@StevenLu 是的,但不幸的是没有关于AJAX/websocket是否使用select或者poll的消息。 - Christopher Schultz
有人可以编写一个使用select()的poll()实现,针对这些环境,但我从未见过这样的实现。Java就是这样做的;-) - Sergey Mashkov
显示剩余2条评论

3

两者都比较慢,而且大部分功能相同,但在大小和某些特性上有所不同!

当你编写迭代器时,每次都需要复制select集合!而poll已经解决了这种问题,使代码更加优美。另一个区别是poll可以默认处理多于1024个文件描述符(FDs)。poll可以处理不同的事件,使程序更易读,而不是使用许多变量来处理此类工作。由于有很多检查,pollselect中的操作都是线性和缓慢的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接