epoll,poll,threadpool有什么区别?

有人能解释一下epollpoll和threadpool有什么不同吗?

  • 有什么优点/缺点?
  • 任何有关框架的build议?
  • 任何build议简单/基本教程?
  • 看来, epollpoll是Linux的具体…是否有一个等效的替代Windows?

Threadpool并不是真的和poll和epoll属于同一个类,所以我会假定你指的是threadpool,就像在“threadpool中每个连接使用一个线程来处理多个连接”一样。

优点和缺点

  • 线程池
    • 合理有效的中小型并发,甚至可以胜过其他技术。
    • 使用多个核心。
    • 即使某些系统(例如Linux)原则上可以安排十万分之一的线程,也不会超过“几百个”。
    • 天真的实施展示了“ 雷鸣群 ”的问题。
    • 除了上下文切换和雷鸣之外,还要考虑记忆。 每个线程都有一个堆栈(通常至less有一个兆字节)。 因此,千线程需要一个千兆字节的RAM来存放堆栈。 即使这个内存没有被提交,它仍然会在32位操作系统下占用相当大的地址空间(64位以下不是问题)。
    • 线程实际上可以使用epoll ,但是显而易见的方式( epoll_wait上的所有线程都是阻塞的)是没有用的,因为epoll会唤醒等待它的每个线程,所以它仍然会有相同的问题。
      • 最佳解决scheme:单线程在epoll上侦听,执行input复用,并将完成的请求交给一个线程池。
      • futex是你的朋友在这里,结合每个线程的快速队列。 尽pipeloggingfutex且笨拙,但futex提供了所需的东西。 epoll可以返回多个事件,而futex可以让你有效地并且以一个精确控制的方式一次唤醒N个被阻塞的线程(理想情况下N是min(num_cpu, num_events) ),最好的情况是它不涉及额外的系统调用/上下文切换。
      • 实施不重要,需要谨慎。
  • fork (又名旧时尚线程池)
    • 合理有效的中小型并发。
    • 没有超过“几百”的规模。
    • 上下文切换更为昂贵(不同的地址空间!)。
    • 对于那些叉子要贵得多的旧系统(所有页面的深层副本)来说,缩放比较严重。 即使在现代系统上, fork也不是“空闲的”,尽pipe开销通常是由写时复制机制合并的。 在也被修改的大型数据集上,相当数量的fork的页面错误可能会对性能产生负面影响。
    • 但是,已经certificate可以工作30多年了。
    • 可笑的易于实施和坚实:如果任何进程崩溃,世界不会结束。 (几乎)没有什么可以做错的。
    • 很容易出现“雷鸣群”。
  • poll / select
    • 两种口味(BSD vs. System V)或多或less都是一样的东西。
    • 有点老,慢,有些尴尬的用法,但实际上没有任何平台不支持它们。
    • 等待一组描述符“发生某些事情”
      • 允许一个线程/进程一次处理多个请求。
      • 没有多核心使用。
    • 每次等待时,都需要将描述符列表从用户复制到内核空间。 需要对描述符执行线性search。 这限制了它的有效性。
    • 不能很好地扩展到“成千上万”(实际上,在大多数系统上硬限制在1024左右,或者在一些系统上低到64)。
    • 使用它,因为它是可移植的,如果你只处理十几个描述符(没有性能问题),或者如果你必须支持没有更好的平台。 不要使用其他方式。
    • 从概念上讲,服务器变得比分叉更复杂一点,因为现在需要为每个连接维护许多连接和一个状态机,并且在请求进来时必须进行多路复用,汇总部分请求等。一个简单的分叉服务器只是知道一个套接字(好吧,两个,计数监听套接字),直到它有它想要的或直到连接被半封闭,然后写任何它想要的。 它不担心阻塞或准备或饥饿,也不担心一些不相关的数据进来,这是其他一些过程的问题。
  • epoll
    • 仅限Linux。
    • 昂贵的修改与高效等待的概念:
      • 添加描述符时将描述符信息复制到内核空间( epoll_ctl
        • 这通常很less发生。
      • 等待事件时不需要将数据复制到内核空间( epoll_wait
        • 这通常是经常发生的事情。
      • 将服务员(或更确切的说是epoll结构)添加到描述符的等待队列中
        • 因此,描述符知道谁在监听,并在适当的时候直接发信号给服务员,而不是服务员search描述符列表
        • poll如何运作的相反方式
        • O(1)关于描述符的数量小(非常快),而不是O(n)
    • timerfdeventfd (惊人的计时器分辨率和精确度)工作得非常好。
    • signalfd很好地结合,消除了信号的尴尬处理,使它们以非常优雅的方式成为正常控制stream程的一部分。
    • epoll实例可以recursion地托pipe其他epoll实例
    • 这个编程模型的假设:
      • 大多数描述符在大部分时间都处于空闲状态,很less有描述符实际发生(如“接收数据”,“连接closures”)。
      • 大多数情况下,您不希望添加/删除设置中的描述符。
      • 大多数时候,你正在等待发生的事情。
    • 一些小的陷阱:
      • 一个级别触发的epoll会唤醒等待它的所有线程(这是“按预期工作”),因此在线程池中使用epoll的方法是毫无用处的。 至less对于一个TCP服务器来说,这并不是什么大问题,因为部分请求将不得不首先被汇编,所以一个简单的multithreading实现是不行的。
      • 不能像文件读/写(“随时准备好”)那样工作。
      • 直到最近才能与AIO一起使用,现在可以通过eventfd ,但需要一个(迄今为止)未eventfdfunction。
      • 如果上述假设成立,则epoll可能效率低下,而poll可能会performance相同或更好。
      • epoll不能做“魔术”,也就是说,就发生事件的数量而言,它仍然必然是O(N)。
      • 但是, epoll与新的recvmmsg系统调用很好地recvmmsg ,因为它一次返回几个准备就绪通知(尽可能多的,直到你指定的maxevents )。 这样就可以在繁忙的服务器上接收一个系统调用的15个EPOLLIN通知,并用第二个系统调用(系统调用减less93%)读取相应的15个消息。 不幸的是,一个recvmmsg调用的所有操作引用相同的套接字,因此它对于基于UDP的服务(对于TCP来说,必须是一种recvmmsmsg系统调用,每个套接字描述符也是recvmmsmsg )。
      • 始终将描述符设置为非阻塞,并且应该检查EAGAIN即使在使用epoll时也是如此,因为存在epoll报告准备就绪以及后续读取(或写入) 仍将阻塞的exception情况。 在某些内核上, poll / select也是这种情况(尽pipe它已经被修复了)。
      • 用一个天真的实现,慢发送者的饥饿是可能的。 当盲目阅读,直到收到通知EAGAIN返回时,可以无限期地读取来自快速发送器的新的传入数据,同时完全挨饿慢发送器(只要数据保持足够快,您可能不会看到EAGAIN而!)。 以相同的方式应用于poll / select
      • 由于文档(包括手册页和TLPI)含糊不清(“很可能”,“应该”,“可能”),有时还会误导其操作,所以边缘触发模式在某些情况下会出现一些怪癖和意外行为。
        该文档指出,等待一个epoll的几个线程都被发送。 它进一步说明了一个通知,告诉你自从上次调用epoll_wait以来是否发生了IO活动(或者自从描述符被打开,如果没有以前的调用)。
        边缘触发模式中的真实的,可观察的行为更接近“唤醒称为epoll_wait第一个线程,这表明IO活动发生在任何人最后在描述符上调用epoll_wait 读/写函数之后发生,并且此后仅报告准备好再次调用下一个线程,或者已经在 epoll_wait 阻塞了任何在描述符上被称为读(或写)函数之后发生的操作“。 这也是有道理的……这不仅仅是文档所build议的。
  • kqueue
    • BSD模拟epoll ,不同的用法,类似的效果。
    • 也适用于Mac OS X
    • 据传速度更快(我从来没有使用过,所以不知道这是真的)。
    • 注册事件并在一个系统调用中返回一个结果集。
  • IO完成端口
    • 用于Windows的Epoll,或者更确切地说类固醇是epoll。
    • 以某种方式(套接字,等待定时器,文件操作,线程,进程)无缝工作,
    • 如果微软在Windows中有一件事,那就是完成端口:
      • 用任意数量的线程开箱即可使用
      • 没有雷鸣般的牛群
      • 以LIFO顺序逐一清醒线索
      • 保持caching温暖并最小化上下文切换
      • 尊重机器上的处理器数量或者提供所需数量的工人
    • 允许应用程序发布事件,这使得它可以实现一个非常简单,高效的并行工作队列实现(在我的系统上每秒计划超过500,000个任务)。
    • 小缺点:一旦添加(不得不closures和重新打开),不会轻易删除文件描述符。

构架

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

ASIO – 如果你在你的项目中使用Boost,那就不要再考虑了:你已经有了boost-asio。

任何build议简单/基本教程?

上面列出的框架带有大量的文档。 Linux 文档和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)工作。 他们正如他们的名字所表明的那样表示完成 ,而不是准备就绪 。 也就是说,你发起了一个asynchronous请求,忘记它,直到一段时间后,你会被告知它已经完成(成功或者没有成功,还有“立即完成”的例外情况)。
使用epoll,你会阻塞,直到你被告知“有些数据”(可能只有一个字节)到达并且可用,或者有足够的缓冲空间,所以你可以做一个没有阻塞的写操作。 只有这样,才会开始实际的操作,然后希望不会阻塞(除了您所期望的,没有严格的保证 – 因此将描述符设置为非阻塞并检查EAGAIN [EAGAIN EWOULDBLOCK对于套接字,因为哦,喜欢,标准允许两个不同的错误值])。