【发布时间】:2011-05-04 20:07:31
【问题描述】:
有人能解释一下epoll、poll 和线程池之间的区别吗?
- 有哪些优点/缺点?
- 对框架有什么建议吗?
- 对简单/基本教程有什么建议吗?
- 似乎
epoll和poll是特定于Linux 的...是否有Windows 的等效替代方案?
【问题讨论】:
标签: asynchronous epoll io-completion-ports
有人能解释一下epoll、poll 和线程池之间的区别吗?
epoll 和poll 是特定于Linux 的...是否有Windows 的等效替代方案?【问题讨论】:
标签: asynchronous epoll io-completion-ports
线程池与 poll 和 epoll 并不真正属于同一类别,因此我假设您指的是线程池,如“线程池处理多个连接,每个连接一个线程”。
epoll,虽然显而易见的方式(所有线程阻塞epoll_wait)是没有用的,因为epoll会唤醒每个线程等待它,所以它仍然会有同样的问题。
futex 是你的朋友,结合例如每个线程的快进队列。尽管记录不充分且笨拙,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 系统调用配合得很好,因为它一次返回多个就绪通知(尽可能多的可用,直到您指定为 maxevents 的任何内容)。这使得可以接收例如在繁忙的服务器上使用一个系统调用发出 15 条 EPOLLIN 通知,并通过第二个系统调用读取相应的 15 条消息(系统调用减少了 93%!)。不幸的是,一个recvmmsg 调用上的所有操作都指向同一个套接字,因此它对于基于UDP 的服务非常有用(对于TCP,必须有一种recvmmsmsg 系统调用,它也为每个项目获取一个套接字描述符! )。epoll 时也应检查EAGAIN,因为在特殊情况下epoll 报告准备就绪并随后进行读取(或写入)将仍然阻塞。在某些内核上poll/select 也是如此(尽管它可能已被修复)。EAGAIN 时,可能会无限期地从快速发送方读取新传入数据,同时完全饿死慢速发送方(只要数据保持足够快,您可能看不到 @987654358 @ 有一会儿!)。以同样的方式应用于poll/select。epoll_wait 以来是否发生了 IO 活动(或者自打开描述符以来,如果没有先前的调用)。epoll_wait 的第一个 线程,表明IO 活动自任何人 上次调用以来已经发生要么 epoll_wait 要么 描述符上的读/写函数,然后只向下一个调用线程或已经阻塞的线程再次报告准备情况 epoll_wait,对于在 anyone 调用描述符上的读取(或写入)函数之后发生的任何操作”。这也有点道理......这与文档所暗示的不完全一样。kqueue
epoll,用法不同,效果相似。libevent -- 2.0版本还支持Windows下的补全端口。
ASIO -- 如果您在项目中使用 Boost,请不要再犹豫:您已经将其作为 boost-asio 提供。
上面列出的框架附带大量文档。 Linuxdocs 和 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)相反。顾名思义,它们表示完成,而不是准备就绪。也就是说,您触发了一个异步请求并忘记了它,直到一段时间后您被告知它已完成(成功或不太成功,还有“立即完成”的例外情况)。
使用 epoll,您会一直阻塞,直到您被通知“一些数据”(可能只有一个字节)已经到达并且可用,或者有足够的缓冲区空间以便您可以在不阻塞的情况下执行写入操作。只有这样,您才开始实际操作,然后希望不会阻塞(与您期望的不同,对此没有严格的保证——因此最好将描述符设置为非阻塞并检查 EAGAIN [EAGAIN and EWOULDBLOCK 用于套接字,因为天哪,标准允许两个不同的错误值])。
【讨论】:
min(num_cpu, num_events) 吗?
min,而不是max——我会改正错字。谢谢。