什么epoll的,调查的区别,线程池? [英] Whats the difference between epoll, poll, threadpool?

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

问题描述

有人能解释的区别是什么epoll的,民意调查之间的线程池?


  • 哪些优点/缺点?

  • 的框架有什么建议?

  • 简单/基本教程有什么建议?

  • 似乎epoll的调查和IST Linux特有的...是否有窗口的等量替代?

非常感谢每一个答案!


解决方案

线程池并没有真正融入同一类别poll和epoll的,所以我会假设你指的是线程池在线程池来处理一个许多连接每个连接主题。

利与弊


  • 线程池

    • 高效合理的中小并发性,甚至可以跑赢其他技术。

    • 利用多个核心。

    • 不能很好地扩展超越数百即使某些系统(如Linux)的原则上可以调度线程的100,000s就好了。

    • 幼稚的做法展品惊群的问题。

    • 除了上下文切换和惊群,必须考虑内存。每个线程有一个堆栈(通常至少一兆字节)。因此,一个一千线程需要的RAM技嘉只是堆栈。即使内存是没有承诺,但仍带走一个32位操作系统下相当大的地址空间(不是真的在64位的问题)。

    • 主题可以的实际使用的epoll ,虽然明显的方式(所有线程上的 epoll_wait )是没有用的,因为epoll的会醒来的每个的线程在等待它,所以它仍然有同样的问题。

      • 最优解:单线程监听epoll的,不输入复用,以及一个线程池的双手完整请求

      • futex的是你的朋友在这里,与结合例如每个线程快进队列。虽然浑身记载和笨拙, futex的提供所需要的到底是什么。 的epoll 可以一次返回若干个事件和 futex的高效和pcisely控制的方式唤醒的$ P $让你的 N 的同时阻塞的线程(N为分(num_cpu,NUM_EVENTS)理想情况下),并在最好的情况下,它不涉及额外的系统调用/上下文切换的。

      • 不容易实现,需要谨慎对待。



  • (又名老款线程池)

    • 高效合理的中小并发。

    • 不能很好地扩展超越几百个。

    • 上下文切换的的更昂贵的(不同的地址空间!)。

    • 秤在旧系统显著恶化,其中叉要贵得多(所有页的深层副本)。即使在现代系统不是免费的,虽然开销主要由写入时复制机制合并。在大型数据集它们的还修改的,以下 叉可能会对性能产生负面影响相当数量的页面错误。

    • 然而,成熟可靠超过30年的工作。

    • 可笑容易实现和坚如磐石的:如果任何进程崩溃,世界并没有结束。有(几乎)没有什么可以做的不对。

    • 很容易出现惊群。


  • 调查 / 选择

    • 两种口味或多或少同样的事情(BSD对系统V)。

    • 有点老又慢,有点尴尬的用法,但几乎没有任何的平台不支持他们。

    • 等待直到有事的一组描述

      • 允许一个线程/进程同时处理许多请求。

      • 无多核心使用。


    • 需要从用户复制描述符表每次等待时间到内核空间。需要执行过描述线性搜索。这限制了其有效性。

    • 不能很好地扩展到千(事实上,1024左右的硬限制在大多数系统中,或者在一些低至64)。

    • 使用它,因为它是便携式如果你只用十几描述应对呢(没有性能问题存在),或者如果你必须支持没有什么更好的平台。不要以其他方式使用。

    • 从概念上讲,服务器变得比分叉一个稍微复杂一点,因为你现在需要维护每个连接的多个连接和一个状态机,你必须请求之间复用,因为他们进来,装配部分的要求,等等。一个简单的分叉服务器只知道一个插槽(当然,二,计数监听套接字),读取直到它有什么希望或者直到连接半封闭,然后写入为所欲为。它不担心堵塞或准备或饥饿,也没有关于未来的一些不相关的数据,这是其他进程的问题。


  • 的epoll

    • 仅限于Linux。

    • 昂贵的修改与高效等待的理念:

      • 将有关描述添加描述时到内核空间信息( epoll_ctl

        • 这通常是发生的事情的很少


      • 确实的的需要复制的数据等待事件的时候到内核空间( epoll_wait

        • 这通常是发生的事情的往往的


      • 添加服务员(或者更确切地说,它epoll的结构)来描述等待队列

        • 描述符,因此知道是谁在听,直接信号服务员合适的,而不是服务员搜索描述符列表时

        • 如何调查的相反的方式工作原理

        • O(1)就描述符的数量小K(非常快),而不是为O(n)



    • 作品非常好, timerfd eventfd (令人惊叹的计时器的分辨率和精度,太)。

    • signalfd 作品很好,消除了信号处理的尴尬,使他们在一个非常优雅的方式正常的控制流的一部分。

    • 一个epoll的实例可以递归承载其他的epoll实例

    • 将这种编程模型所作的​​假设:

      • 大多数的描述都是闲置的大部分时间,几件事情(接收数据例如,连不上)上的一些描述实际发生。

      • 在大多数情况下,你不希望从集合添加/删除描述符。

      • 大多数时候,你等待事情发生。


    • 一些小的缺陷:

      • 电平触发epoll的唤醒等待所有线程(这是如预期运作),所以用线程池也没用使用epoll的天真的方式。至少对于TCP服务器,这是没有什么大问题,因为部分请求无论如何都会进行组装第一,所以天真多线程的实现不会做任何一种方式。

      • 不工作的人会用文件的读期望/写(时刻准备着)。

      • 无法与AIO通过 eventfd 使用直到最近,现在有可能的,但需要一个(至今)无证功能。

      • 如果上述假设的的真实,epoll的可能是低效的,而调查可同样或更好的执行。

      • 的epoll 不能做神奇,也就是说,它仍然是必然O(N)相对于的的数量发生的事件

      • 然而,的epoll 的recvmmsg 系统调用打得很好,因为它一次返回多个就绪通知(多达可用,到任何你指定为 maxevents )。这使得有可能以接收例如15 EPOLLIN通知一个繁忙的服务器上的一个系统调用,以及读取相应信息15与第二系统调用(系统调用中减少了93%!)。不幸,所有的操作的一个的recvmmsg invokation指同一插座,所以它是基于UDP的服务是非常有用(对于TCP,就必须是一种<$的C $ C> recvmmsmsg 系统调用这也需要每个项目一个socket描述符!)。

      • 描述符应的总是的使用,即使设置为非阻塞,一个应该检查 EAGAIN 的epoll ,因为有其中的epoll 报告准备和后续的读取(或写)特殊情况下会的还是的块。这也是 / 调查选择上的一些内核的情况下(尽管它有presumably得到修复)。

      • 幼稚的实施,慢发件人的饥饿是可能的。如果一味地读,直到 EAGAIN 是在接到通知回来,就可以从快速发送无限期读新传入的数据,而完全挨饿一个缓慢的发送者(只要数据维持在未来的速度不够快,你可能看不到 EAGAIN 好一阵子!)。适用于调查 / 选择以同样的方式。

      • 边沿触发模式有一些怪癖,在某些情况下意外的行为,因为文档(包括手册页和TLPI)是模糊的(可能,应该,可能),有时误导有关其操作。< BR>
        该文件指出,多个线程在等待在一个epoll的全部信号。它进一步指出,一个通知告诉你IO活动是否已经因为对最后一次通话发生 epoll_wait (或自描述被打开了,如果没有previous调用) 。结果
        在边沿触发模式真正的,观察到的行为更接近唤醒的第一个的线程已调用 epoll_wait ,这表明IO活动已经发生自的任何的最后调用的或者 epoll_wait 的读/写的描述符的功能,此后仅报告准备再次的下一个线程调用或已阻塞 epoll_wait ,为之后发生的任何操作的任何的所谓的读(或写)上的描述符函数。它种是有道理的,太...它仅仅是不完全的文档暗示什么。



  • 的kqueue

    • BSD analogon到的epoll ,不同的用法,类似的效果。

    • ,也适用于Mac OS X

    • 此地据说是更快(我从来没有使用过,所以不能告诉如果这是真的)。​​

    • 注册事件,并返回一个单一的系统调用的结果集。


  • IO完成端口

    • 是Epoll适用于Windows,或者说类固醇epoll的。

    • 与无缝协同工作的所有的是在某些方面可等待或可报警(插座,可等待计时器,文件操作,线程,进程)

    • 如果微软权在Windows了一件事,那就是完成端口:

      • 作品无忧开箱与任意数量的线程

      • 无惊群

      • 线程醒来逐一在LIFO顺序

      • 保持高速缓存温暖,最大限度地减少上下文切换

      • 尊重机器上的处理器数量或交付劳动者的期望数量


    • 允许应用程序发布的事件,这适合于一个非常容易,故障安全,高效的并行工作队列执行(时间表向上每500,000次任务,我的系统上)。

    • 小缺点:不容易去除,一旦增加文件描述符(必须关闭并重新打开)


框架

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

ASIO - 如果你在​​项目中使用升压,没有进一步看:你已经有这个可作为升压​​ASIO <。 / p>

任何简单/基本教程建议吗?

以上所列配备了大量文档的框架。 Linux的文档和MSDN广泛epoll的解释和完成端口。

迷你教程使用epoll的:

  INT my_epoll = epoll_create(0); //参数是时下忽略epoll_eventê;
e.fd = some_socket_fd; //这个其实可以是任何你喜欢的epoll_ctl(my_epoll,EPOLL_CTL_ADD,some_socket_fd,急症室);...
epoll_event EVT [10]; //或任何号码
对于(...)
    如果((NUM = epoll_wait(my_epoll,EVT,10,-1))大于0)
        做一点事();

迷你教程IO完成端口(注意使用不同的参数调用CreateIoCompletionPort来两次):

  HANDLE IOCP = CreateIoCompletionPort来(INVALID_HANDLE_VALUE,0,0,0); //等于epoll_create
CreateIoCompletionPort来(mySocketHandle,IOCP,0,0); //等于epoll_ctl(EPOLL_CTL_ADD)OVERLAPPED O;
对于(...)
    如果(GetQueuedCompletionStatus时(IOCP,和放大器; number_bytes,和放大器;键,&安培; 0,INFINITE))//等于epoll_wait()
        做一点事();

(这些微型TUTS省略所有类型的错误检查,并且希望我没有做任何错别字,但他们应该大部分被确定,给你一些想法。)

编辑:结果
需要注意的是完成端口(Windows)中概念合作的其他方式的epoll(或的kqueue)左右。他们发出信号,顾名思义,的完成的,不是的就绪的。也就是说,你火了一个异步请求,并忘掉它,直到一段时间后,有人告诉你,它已经完成(无论是成功还是没有那么多成功,且有立即完成太特殊情况下)。

随着epoll的,你阻塞,直到你被告知,要么一些数据(可能为一字节)已到达,可还是有足够的缓冲空间,所以你可以做一个写操作而不阻塞。只有这样,你就开始实际操作,然后将有望不会阻止(而不是你所期望的,对于没有严格的保证 - 因此要设置的描述符为非阻塞和检查EAGAIN [EAGAIN的
的EWOULDBLOCK的插座,因为哦喜悦,该标准允许两个不同的误差值])。

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 and poll ist linux specific... Is there an equivalent alternative for windows?

Thanks a lot for every answer!

解决方案

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

  • 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 (a.k.a. old fashion threadpool)
    • 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".
  • poll / select
    • 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.
    • Needs to copy list of descriptors from user to kernel space every time you wait. Needs to perform a linear search over descriptors. This limits its effectiveness.
    • Does not scale well to "thousands" (in fact, hard limit around 1024 on most systems, or as low as 64 on some).
    • Use it because it's portable if you only deal with a dozen descriptors anyway (no performance issues there), or if you must support platforms that don't have anything better. Don't use otherwise.
    • Conceptually, a server becomes a little more complicated than a forked one, since you now need to maintain many connections and a state machine for each connection, and you must multiplex between requests as they come in, assemble partial requests, etc. A simple forked server just knows about a single socket (well, two, counting the listening socket), reads until it has what it wants or until the connection is half-closed, and then writes whatever it wants. It doesn't worry about blocking or readiness or starvation, nor about some unrelated data coming in, that's some other process's problem.
  • epoll
    • 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.
      • Does not need to copy data to kernel space when waiting for events (epoll_wait)
        • This is usually something that happens very often.
      • Adds the waiter (or rather its epoll structure) to descriptors' wait queues
        • 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)
    • Works very well with timerfd and eventfd (stunning timer resolution and accuracy, too).
    • Works nicely with signalfd, eliminating the awkward handling of signals, making them part of the normal control flow in a very elegant manner.
    • An epoll instance can host other epoll instances recursively
    • Assumptions made by this programming model:
      • Most descriptors are idle most of the time, few things (e.g. "data received", "connection closed") actually happen on few descriptors.
      • Most of the time, you don't want to add/remove descriptors from the set.
      • Most of the time, you're waiting on something to happen.
    • Some minor pitfalls:
      • 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.
  • kqueue
    • 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.
  • IO Completion ports
    • 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
    • Allows the application to post events, which lends itself to a very easy, failsafe, and efficient parallel work queue implementation (schedules upwards of 500,000 tasks per second on my system).
    • Minor disadvantage: Does not easily remove file descriptors once added (must close and re-open).

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:

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

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

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

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

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