有人能解释一下 epoll
、 poll
和线程池之间的区别吗?
epoll
和 poll
是特定于 Linux 的......是否有 Windows 的等效替代方案? 最佳答案
线程池与 poll 和 epoll 并不真正属于同一类别,因此我假设您将线程池称为“线程池以每个连接一个线程处理多个连接”。
利弊
epoll
,虽然明显的方式(所有线程都阻塞在 epoll_wait
上)是没有用的,因为 epoll 会唤醒每个等待它的线程,所以它仍然会有同样的问题。futex
是你这里的 friend ,结合例如每个线程一个快进队列。尽管记录不完整且笨拙,但 futex
确实提供了所需的内容。 epoll
可能一次返回多个事件,而 futex
可让您以有效且精确控制的方式一次唤醒 N 个阻塞的线程(理想情况下,N 为 min(num_cpu, num_events)
),并且在最好的情况下,它不涉及系统调用/上下文切换全部。 fork
(又名旧式线程池)fork
也不是“免费”的,尽管开销主要由写时复制机制合并。在也被修改的大型数据集上,fork
之后的大量页面错误可能会对性能产生负面影响。 poll
/select
epoll
epoll_ctl
)epoll_wait
)poll
工作原理的相反方式 timerfd
和 eventfd
配合得非常好(令人惊叹的计时器分辨率和准确性,也是)。 signalfd
很好地工作,消除了信号的尴尬处理,使它们以非常优雅的方式成为正常控制流的一部分。 eventfd
使用,但需要(迄今为止)未记录的功能。 poll
可能表现相同或更好。 epoll
不能做“魔术”,即它仍然需要 O(N) 相对于发生的事件数量。 epoll
与新的 recvmmsg
系统调用配合得很好,因为它一次返回几个准备就绪通知(尽可能多,直到你指定的任何 0x31184321)。这使得可以接收例如在繁忙的服务器上使用一个系统调用发出 15 条 EPOLLIN 通知,并使用第二个系统调用读取相应的 15 条消息(系统调用减少了 93%!)。不幸的是,对一个 maxevents
调用的所有操作都指向同一个套接字,因此它主要用于基于 UDP 的服务(对于 TCP,必须有一种 recvmmsg
系统调用,每个项目也需要一个套接字描述符!)。 recvmmsmsg
时也应检查 EAGAIN
,因为在特殊情况下 0x2518122313431 仍会报告读取(或写入)后的 a441 仍会阻塞读取。某些内核上的 epoll
/epoll
也是这种情况(尽管它可能已被修复)。 poll
时,有可能无限期地从快速发送方读取新传入的数据,同时完全饿死慢速发送方(只要数据保持足够快,您可能在很长一段时间内都看不到 select
尽管!)。以同样的方式应用于 EAGAIN
/EAGAIN
。 该文档指出,等待一个 epoll 的多个线程都已发出信号。它进一步指出,通知会告诉您自上次调用
poll
(或自描述符打开以来,如果之前没有调用)是否发生了 IO 事件。边缘触发模式下真实的、可观察的行为更接近于“唤醒调用
select
的第一个线程,表明自上次调用 epoll_wait
或描述符上的读/写函数以来发生了 IO 事件,此后仅报告再次准备好下一个线程调用或已在 epoll_wait
中阻塞,以便在任何人调用描述符上的读(或写)函数之后发生的任何操作”。这也有点道理……这与文档所建议的不完全相同。 epoll_wait
epoll_wait
,用法不同,效果相似。 构架
libevent -- 2.0版本也支持Windows下的补全端口。
ASIO -- 如果你在你的项目中使用了 Boost,那就别无所求:你已经有了它作为 boost-asio。
对简单/基本教程有什么建议吗?
上面列出的框架带有大量文档。 Linux docs 和 MSDN 广泛解释了 epoll 和完成端口。
epoll 使用小教程:
int my_epoll = epoll_create(0); // argument is ignored nowadays
epoll_event e;
e.fd = some_socket_fd; // this can in fact be anything you like
epoll_ctl(my_epoll, EPOLL_CTL_ADD, some_socket_fd, &e);
...
epoll_event evt[10]; // or whatever number
for(...)
if((num = epoll_wait(my_epoll, evt, 10, -1)) > 0)
do_something();
IO 完成端口的迷你教程(注意使用不同的参数调用 CreateIoCompletionPort 两次):
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); // equals epoll_create
CreateIoCompletionPort(mySocketHandle, iocp, 0, 0); // equals epoll_ctl(EPOLL_CTL_ADD)
OVERLAPPED o;
for(...)
if(GetQueuedCompletionStatus(iocp, &number_bytes, &key, &o, INFINITE)) // equals epoll_wait()
do_something();
(这些迷你图省略了所有类型的错误检查,希望我没有打错任何字,但它们在大多数情况下应该可以给你一些想法。)
编辑:
请注意,完成端口(Windows)在概念上与 epoll(或 kqueue)相反。顾名思义,它们表示完成,而不是准备就绪。也就是说,您触发一个异步请求并忘记它,直到一段时间后您被告知它已完成(成功或不那么成功,也有“立即完成”的异常(exception)情况)。
使用 epoll,您会一直阻塞,直到您收到“某些数据”(可能只有一个字节)已经到达并且可用或者有足够的缓冲区空间以便您可以在不阻塞的情况下执行写操作的通知。只有这样,您才开始实际操作,然后希望它不会阻塞(除了您所期望的,没有严格的保证——因此将描述符设置为非阻塞并检查 EAGAIN [EAGAIN 和 EWOULDBLOCK对于套接字,因为哦,很高兴,标准允许两个不同的错误值])。
关于asynchronous - epoll、poll、threadpool 有什么区别?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4093185/