epoll,poll,threadpool有什么区别? [英] What's the difference between epoll, poll, threadpool?

查看:119
本文介绍了epoll,poll,threadpool有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人可以解释epollpoll和线程池之间的区别吗?

Could someone explain what the difference is between epoll, poll and threadpool?

  • 优点/缺点是什么?
  • 对框架有何建议?
  • 对简单/基础教程有什么建议吗?
  • epollpoll似乎是特定于Linux的... Windows是否有等效的替代品?
  • What are the pros / cons?
  • Any suggestions for frameworks?
  • Any suggestions for simple/basic tutorials?
  • It seems that epoll and poll are Linux-specific... Is there an equivalent alternative for Windows?

推荐答案

线程池实际上与poll和epoll不在同一类别,因此我假设您所引用的线程池与"threadpool"中的处理一个线程的许多连接"相同.每个连接的线程数".

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".

  • 线程池
    • 对于中小型并发来说相当有效,甚至可以胜过其他技术.
    • 使用多个内核.
    • 即使某些系统(例如Linux)原则上可以很好地调度100,000个线程,也不能很好地扩展到几百个".
    • 天真的实现方式出现"雷电群"问题.
    • 除了上下文切换和雷声群外,还必须考虑内存.每个线程都有一个堆栈(通常至少为一个兆字节).因此,一千个线程仅用于堆栈就需要一个GB的RAM.即使未提交该内存,在32位操作系统下,它仍然会占用相当大的地址空间(在64位操作系统下,这并不是真正的问题).
    • 线程可以实际上使用epoll,尽管显然的方法(所有线程在epoll_wait上阻塞)没有用,因为epoll会唤醒每个线程等待它,因此它仍然会有相同的问题.
      • 最佳解决方案:单线程侦听epoll,执行输入多路复用,并将完成的请求移交给线程池.
      • futex是您的朋友,例如每个线程一个快速转发队列.尽管记录不清且笨拙,但futex确实提供了所需的内容. epoll可能一次返回多个事件,并且futex允许您高效且以精确控制的方式一次唤醒 N 个被阻塞的线程(理想情况下,N为min(num_cpu, num_events)),并且在最好的情况是,它根本不需要额外的syscall/context开关.
      • 实施起来很简单,要格外小心.
      • threadpool
        • Reasonably efficient for small and medium concurrency, can even outperform other techniques.
        • Makes use of multiple cores.
        • Does not scale well beyond "several hundreds" even though some systems (e.g. Linux) can in principle schedule 100,000s of threads just fine.
        • Naive implementation exhibits "thundering herd" problem.
        • Apart from context switching and thundering herd, one must consider memory. Each thread has a stack (typically at least a megabyte). A thousand threads therefore take a gigabyte of RAM just for stack. Even if that memory is not committed, it still takes away considerable address space under a 32 bit OS (not really an issue under 64 bits).
        • Threads can actually use epoll, though the obvious way (all threads block on epoll_wait) is of no use, because epoll will wake up every thread waiting on it, so it will still have the same issues.
          • Optimal solution: single thread listens on epoll, does the input multiplexing, and hands complete requests to a threadpool.
          • 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, and futex lets you efficiently and in a precisely controlled manner wake N blocked threads at a time (N being min(num_cpu, num_events) ideally), and in the best case it does not involve an extra syscall/context switch at all.
          • Not trivial to implement, takes some care.
          • 中小型并发非常有效.
          • 扩展范围远远超过几百个".
          • 上下文切换要昂贵得多(不同的地址空间!).
          • 在Fork昂贵得多(所有页面的深层副本)的旧系统上,扩展性明显变差.即使在现代系统上,fork也不是免费的",尽管开销主要是由写时复制机制合并而成的.在也已修改的大型数据集上,fork之后的大量页面错误可能会对性能产生负面影响.
          • 不过,事实证明,它可以可靠地工作30多年.
          • 非常容易实现且坚如磐石:如果任何进程崩溃,世界将不会终结. (几乎)您无能为力.
          • 非常容易发生雷声大浪".
          • Reasonably efficient for small and medium concurrency.
          • Does not scale well beyond "few hundreds".
          • Context switches are much more expensive (different address spaces!).
          • Scales significantly worse on older systems where fork is much more expensive (deep copy of all pages). Even on modern systems 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 following fork may negatively impact performance.
          • However, proven to work reliably for over 30 years.
          • Ridiculously easy to implement and rock solid: If any of the processes crash, the world does not end. There is (almost) nothing you can do wrong.
          • Very prone to "thundering herd".
          • 两种味道(BSD与System V)大致相同.
          • 有些古老和缓慢,用法有些笨拙,但是实际上没有平台不支持它们.
          • 在一组描述符上等待直到某事发生"
            • 允许一个线程/进程一次处理多个请求.
            • 没有多核用法.
            • Two flavours (BSD vs. System V) of more or less the same thing.
            • Somewhat old and slow, somewhat awkward usage, but there is virtually no platform that does not support them.
            • Waits until "something happens" on a set of descriptors
              • Allows one thread/process to handle many requests at a time.
              • No multi-core usage.
              • 仅Linux.
              • 昂贵的修改与有效的等待的概念:
                • 添加描述符时将有关描述符的信息复制到内核空间(epoll_ctl)
                  • 这通常是罕见发生的事情.
                  • Linux only.
                  • Concept of expensive modifications vs. efficient waits:
                    • Copies information about descriptors to kernel space when descriptors are added (epoll_ctl)
                      • This is usually something that happens rarely.
                      • 这通常是经常发生的事情.
                      • 描述符因此知道谁在监听,并在适当时直接向服务员发送信号,而不是让服务员搜索描述符列表
                      • poll工作方式的相反方式
                      • O(1)的描述符数量小k(非常快),而不是O(n)
                      • Descriptor therefore knows who is listening and directly signals waiters when appropriate rather than waiters searching a list of descriptors
                      • Opposite way of how poll works
                      • O(1) with small k (very fast) in respect of the number of descriptors, instead of O(n)
                      • 大多数描述符大多数时间都是空闲的,很少有描述符(例如数据接收",连接已关闭")发生.
                      • 大多数时候,您不想从集合中添加/删除描述符.
                      • 大多数时候,您都在等待某些事情发生.
                      • 级别触发的epoll唤醒等待它的所有线程(这是按预期的方式工作"),因此,将epoll与线程池一起使用的幼稚方式是无用的.至少对于TCP服务器而言,这不是什么大问题,因为无论如何都必须首先组装部分请求,因此,幼稚的多线程实现不会做任何一种方式.
                      • 不能像人们期望的那样进行文件读/写操作(始终准备就绪").
                      • 直到最近才可以与AIO一起使用,现在可以通过eventfd来使用,但是需要(迄今为止)未记录的功能.
                      • 如果以上假设 not 不成立,则epoll可能会效率低下,并且poll的效果可能相同或更好.
                      • epoll不能做魔术",即,相对于发生的事件数量,它仍然必须为O(N).
                      • 但是,epoll与新的recvmmsg syscall配合得很好,因为它一次返回多个准备就绪通知(尽可能多的可用通知,直至您指定为maxevents的任何内容).这使得可以接收例如在繁忙的服务器上使用一个syscall发出15条EPOLLIN通知,并通过第二个syscall读取相应的15条消息(syscall减少93%!).不幸的是,对一个recvmmsg调用的所有操作都引用相同的套接字,因此它对于基于UDP的服务最有用(对于TCP,将必须有一种recvmmsmsg syscall,它也需要为每个项目使用一个套接字描述符! ).
                      • 描述符应始终设置为非阻塞,并且即使使用epoll时也应检查EAGAIN,因为在某些特殊情况下,epoll报告准备就绪并随后进行读(或写)操作将静止阻止.在某些内核上,poll/select也是这种情况(尽管它可能已修复).
                      • 使用天真实现,可能会使慢速发送者饿死.当盲目读取直到收到通知后返回EAGAIN时,有可能无限期地从快速发送者读取新的传入数据,而完全饿死了缓慢的发送者(只要数据保持足够快的速度,您可能看不到相当长的时间!).以相同的方式适用于poll/select.
                      • 在某些情况下,边缘触发模式具有一些怪癖和意外行为,因为文档(手册页和TLPI)含糊不清(可能",应该",可能"),有时会误导其操作. br> 该文档指出,正在等待一个epoll的多个线程都已发出信号.它进一步指出,通知会告诉您自上次调用epoll_wait以来(或者如果没有先前调用,则从打开描述符开始)是否发生了IO活动. 边缘触发模式下的真实,可观察到的行为更接近于唤醒调用了epoll_wait first 线程,这表明自任何人上次调用以来,IO活动已经发生 epoll_wait 描述符上的读/写功能,此后仅向下一个调用或已被阻塞的线程再次报告准备情况. epoll_wait,对于任何人在描述符上调用读(或写)函数的任何操作".也是有道理的……这恰恰不是文档中所建议的.
                      • A level-triggered epoll wakes all threads waiting on it (this is "works as intended"), therefore the naive way of using epoll with a threadpool is useless. At least for a TCP server, it is no big issue since partial requests would have to be assembled first anyway, so a naive multithreaded implementation won't do either way.
                      • Does not work as one would expect with file read/writes ("always ready").
                      • Could not be used with AIO until recently, now possible via eventfd, but requires a (to date) undocumented function.
                      • If the above assumptions are not true, epoll can be inefficient, and 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.
                      • However, epoll plays well with the new recvmmsg syscall, since it returns several readiness notifications at a time (as many as are available, up to whatever you specify as maxevents). 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 one recvmmsg invokation refer to the same socket, so it is mostly useful for UDP based services (for TCP, there would have to be a kind of recvmmsmsg syscall which also takes a socket descriptor per item!).
                      • Descriptors should always be set to nonblocking and one should check for EAGAIN even when using epoll because there are exceptional situations where epoll reports readiness and a subsequent read (or write) will still block. This is also the case for poll/select on some kernels (though it has presumably been fixed).
                      • With a naive implementation, starvation of slow senders is possible. When blindly reading until 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 see EAGAIN for quite a while!). Applies to poll/select in the same manner.
                      • Edge-triggered mode has some quirks and unexpected behaviour in some situations, since the documentation (both man pages and TLPI) is vague ("probably", "should", "might") and sometimes misleading about its operation.
                        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 either epoll_wait or a read/write function on the descriptor, and thereafter only reports readiness again to the next thread calling or already blocked in epoll_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.
                      • BSD与epoll类似,用法不同,效果相似.
                      • 也可以在Mac OS X上使用
                      • 要提高速度(我从未使用过它,所以无法确定是不是真的).​​
                      • 注册事件并在单个系统调用中返回结果集.
                      • BSD analogon to epoll, different usage, similar effect.
                      • Also works on Mac OS X
                      • Rumoured to be faster (I've never used it, so cannot tell if that is true).
                      • Registers events and returns a result set in a single syscall.
                      • 适用于Windows的Epoll,或者更确切地说,适用于类固醇的epoll.
                      • 与可以通过某种方式(套接字,可等待的计时器,文件操作,线程,进程)等待或发出警报的一切无缝工作
                      • 如果Microsoft在Windows中解决了一件事情,那就是完成端口:
                        • 任意数量的线程即可立即使用,无需担心
                        • 没有雷声群
                        • 按LIFO顺序逐个唤醒线程
                        • 保持高速缓存并最大程度地减少上下文切换
                        • 尊重机器上的处理器数量或提供所需数量的工人
                        • Epoll for Windows, or rather epoll on steroids.
                        • Works seamlessly with everything that is waitable or alertable in some way (sockets, waitable timers, file operations, threads, processes)
                        • If Microsoft got one thing right in Windows, it is completion ports:
                          • Works worry-free out of the box with any number of threads
                          • No thundering herd
                          • Wakes threads one by one in a LIFO order
                          • Keeps caches warm and minimizes context switches
                          • Respects number of processors on machine or delivers the desired number of workers

                          libevent - 2.0版还支持Windows下的完成端口.

                          libevent -- The 2.0 version also supports completion ports under Windows.

                          ASIO -如果您在项目中使用Boost,则无需再看: boost-asio.

                          ASIO -- If you use Boost in your project, look no further: You already have this available as boost-asio.

                          上面列出的框架带有大量的文档. Linux 文档和MSDN解释了epoll和完工港口.

                          The frameworks listed above come with extensive documentation. The Linux docs and MSDN explains epoll and completion ports extensively.

                          使用epoll的迷你教程:

                          Mini-tutorial for using 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):

                          Mini-tutorial for IO completion ports (note calling CreateIoCompletionPort twice with different parameters):

                          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();
                          

                          (这些小型工具忽略了所有类型的错误检查,希望我没有做任何错别字,但在大多数情况下它们应该可以给您一些想法.)

                          (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.)


                          请注意,完成端口(Windows)在概念上与epoll(或kqueue)相反.顾名思义,它们表示完成,而不是准备就绪.也就是说,您触发一个异步请求,然后将其忽略,直到一段时间后,您会被告知该请求已完成(要么成功,要么不是很成功,并且还有立即完成"的例外情况).
                          使用epoll,您将阻塞直到通知您某些数据"(可能只有一个字节)到达并且可用,或者有足够的缓冲区空间,这样您就可以进行写操作而不会阻塞.只有到那时,您才开始实际操作,然后希望该操作不会阻塞(除了您期望的那样,没有严格的保证—因此,将描述符设置为非阻塞并检查EAGAIN [EAGAIN EWOULDBLOCK用于套接字,因为很高兴,该标准允许两个不同的错误值]).


                          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]).

                          这篇关于epoll,poll,threadpool有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆