epoll、poll、线程池有什么区别?
有人可以解释一下epoll
、poll
和线程池之间的区别吗?
- 有什么优点/缺点?
- 对框架有什么建议吗?
- 对于简单/基本的教程有什么建议吗?
- 看起来
epoll
和poll
是 Linux 特定的...... Windows 是否有等效的替代方案?
Could someone explain what the difference is between epoll
, poll
and threadpool?
- What are the pros / cons?
- Any suggestions for frameworks?
- Any suggestions for simple/basic tutorials?
- It seems that
epoll
andpoll
are Linux-specific... Is there an equivalent alternative for Windows?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
线程池并不真正属于与 poll 和 epoll 相同的类别,因此我假设您指的是线程池,如“线程池处理多个连接,每个连接一个线程”。
的优缺点
epoll
,尽管显而易见的方法(所有线程都在epoll_wait
上阻塞)没有用,因为epoll会唤醒每个线程都在等待它,所以它仍然会遇到相同的问题。futex
是您的朋友,例如与每个线程的快进队列相结合。尽管文档记录很差且难以操作,但futex
确实提供了所需的内容。epoll
可能一次返回多个事件,而futex
可以让您以精确控制的方式高效且一次唤醒 N 个阻塞线程(N理想情况下为 min(num_cpu, num_events)),并且在最好的情况下,它根本不涉及额外的系统调用/上下文切换。fork
(又名老式线程池)fork
之后的大量页面错误可能会对性能产生负面影响。投票
/选择
epoll
epoll_ctl
)epoll_wait
)轮询
工作方式相反的方式timerfd
和eventfd
配合得很好(计时器分辨率和准确性也令人惊叹)。signalfd
配合良好,消除了信号的尴尬处理,使它们以非常优雅的方式成为正常控制流的一部分。eventfd
来使用,但需要一个(迄今为止)未记录的函数。poll
的性能可能相同或更好。epoll
无法实现“魔法”,即就发生的事件数量而言,它仍然必然是 O(N)。epoll
与新的recvmmsg
系统调用配合得很好,因为它一次返回多个就绪通知(可用的数量最多,最多可达您指定的数量)最大事件
)。这使得可以在繁忙的服务器上使用一个系统调用接收例如 15 个 EPOLLIN 通知,并使用第二个系统调用读取相应的 15 条消息(系统调用减少 93%!)。不幸的是,一个recvmmsg
调用上的所有操作都引用相同的套接字,因此它对于基于 UDP 的服务最有用(对于 TCP,必须有一种recvmmsmsg
系统调用还需要每个项目的套接字描述符!)。epoll
时也应该检查EAGAIN
,因为在某些特殊情况下epoll
报告就绪情况,随后的读取(或写入)将仍然阻塞。某些内核上的poll
/select
也是如此(尽管它可能已被修复)。EAGAIN
时,有可能无限期地从快速发送方读取新传入的数据,同时让慢速发送方完全挨饿(只要数据保持足够快的速度,您就可以可能很长一段时间都看不到EAGAIN
!)。以同样的方式适用于poll
/select
。文档指出,等待一个 epoll 的多个线程都会收到信号。它进一步指出,通知会告诉您自上次调用
epoll_wait
以来是否发生了 IO 活动(或者自描述符打开以来,如果没有先前的调用)。边缘触发模式中真实的、可观察到的行为更接近于“唤醒调用了
epoll_wait
的第一个线程,表明自任何人<以来已经发生了 IO 活动。 /em> 最后一次调用epoll_wait
或描述符上的读/写函数,此后仅再次向下一个报告就绪情况对于任何人调用描述符上的读(或写)函数之后发生的任何操作,线程调用或已阻塞在epoll_wait
中”。这也有点道理……只是不完全符合文档的建议。kqueue
epoll
,不同的用法,相似的效果。框架
libevent -- 2.0 版本还支持 Windows 下的完成端口。
ASIO -- 如果您在项目中使用 Boost,无需再犹豫:您已经可以将其用作 boost-asio 。
对于简单/基本的教程有什么建议吗?
上面列出的框架附带了大量文档。 Linux docs 和 MSDN 解释了 epoll和完成端口广泛。
使用 epoll 的迷你教程:
IO 完成端口的迷你教程(注意使用不同的参数调用 CreateIoCompletionPort 两次):(
这些迷你图省略了所有类型的错误检查,希望我没有犯任何拼写错误,但它们应该用于大部分可以给你一些想法。)
编辑:
请注意,完成端口 (Windows) 在概念上与 epoll(或 kqueue)相反。正如其名称所示,它们表示“完成”,而不是“准备就绪”。也就是说,您发出一个异步请求并忘记它,直到一段时间后您被告知它已完成(成功或不太成功,并且也存在“立即完成”的特殊情况)。
使用 epoll,您会阻塞,直到收到通知“某些数据”(可能只有一个字节)已到达并且可用,或者有足够的缓冲区空间,以便您可以在不阻塞的情况下执行写入操作。只有这样,您才开始实际操作,然后希望该操作不会阻塞(与您期望的不同,对此没有严格的保证 - 因此,最好将描述符设置为非阻塞并检查 EAGAIN [EAGAIN 和 用于套接字的 EWOULDBLOCK,因为哦,高兴的是,该标准允许两个不同的错误值])。
Threadpool does not really fit into the same category as poll and epoll, so I will assume you are referring to threadpool as in "threadpool to handle many connections with one thread per connection".
Pros and cons
epoll
, though the obvious way (all threads block onepoll_wait
) is of no use, because epoll will wake up every thread waiting on it, so it will still have the same issues.futex
is your friend here, in combination with e.g. a fast forward queue per thread. Although badly documented and unwieldy,futex
offers exactly what's needed.epoll
may return several events at a time, andfutex
lets you efficiently and in a precisely controlled manner wake N blocked threads at a time (N beingmin(num_cpu, num_events)
ideally), and in the best case it does not involve an extra syscall/context switch at all.fork
(a.k.a. old fashion threadpool)fork
is not "free", although the overhead is mostly coalesced by the copy-on-write mechanism. On large datasets which are also modified, a considerable number of page faults followingfork
may negatively impact performance.poll
/select
epoll
epoll_ctl
)epoll_wait
)poll
workstimerfd
andeventfd
(stunning timer resolution and accuracy, too).signalfd
, eliminating the awkward handling of signals, making them part of the normal control flow in a very elegant manner.eventfd
, but requires a (to date) undocumented function.poll
may perform equally or better.epoll
cannot do "magic", i.e. it is still necessarily O(N) in respect to the number of events that occur.epoll
plays well with the newrecvmmsg
syscall, since it returns several readiness notifications at a time (as many as are available, up to whatever you specify asmaxevents
). This makes it possible to receive e.g. 15 EPOLLIN notifications with one syscall on a busy server, and read the corresponding 15 messages with a second syscall (a 93% reduction in syscalls!). Unluckily, all operations on onerecvmmsg
invokation refer to the same socket, so it is mostly useful for UDP based services (for TCP, there would have to be a kind ofrecvmmsmsg
syscall which also takes a socket descriptor per item!).EAGAIN
even when usingepoll
because there are exceptional situations whereepoll
reports readiness and a subsequent read (or write) will still block. This is also the case forpoll
/select
on some kernels (though it has presumably been fixed).EAGAIN
is returned upon receiving a notification, it is possible to indefinitely read new incoming data from a fast sender while completely starving a slow sender (as long as data keeps coming in fast enough, you might not seeEAGAIN
for quite a while!). Applies topoll
/select
in the same manner.The documentation states that several threads waiting on one epoll are all signalled. It further states that a notification tells you whether IO activity has happened since the last call to
epoll_wait
(or since the descriptor was opened, if there was no previous call).The true, observable behaviour in edge-triggered mode is much closer to "wakes the first thread that has called
epoll_wait
, signalling that IO activity has happened since anyone last called eitherepoll_wait
or a read/write function on the descriptor, and thereafter only reports readiness again to the next thread calling or already blocked inepoll_wait
, for any operations happening after anyone called a of read (or write) function on the descriptor". It kind of makes sense, too... it just isn't exactly what the documentation suggests.kqueue
epoll
, different usage, similar effect.Frameworks
libevent -- The 2.0 version also supports completion ports under Windows.
ASIO -- If you use Boost in your project, look no further: You already have this available as boost-asio.
Any suggestions for simple/basic tutorials?
The frameworks listed above come with extensive documentation. The Linux docs and MSDN explains epoll and completion ports extensively.
Mini-tutorial for using epoll:
Mini-tutorial for IO completion ports (note calling CreateIoCompletionPort twice with different parameters):
(These mini-tuts omit all kind of error checking, and hopefully I didn't make any typos, but they should for the most part be ok to give you some idea.)
EDIT:
Note that completion ports (Windows) conceptually work the other way around as epoll (or kqueue). They signal, as their name suggests, completion, not readiness. That is, you fire off an asynchronous request and forget about it until some time later you're told that it has completed (either successfully nor not so much successfully, and there is the exceptional case of "completed immediately" too).
With epoll, you block until you are notified that either "some data" (possibly as little as one byte) has arrived and is available or there is sufficient buffer space so you can do a write operation without blocking. Only then, you start the actual operation, which then will hopefully not block (other than you would expect, there is no strict guarantee for that -- it is therefore a good idea to set descriptors to nonblocking and check for EAGAIN [EAGAIN and EWOULDBLOCK for sockets, because oh joy, the standard allows for two different error values]).