事件驱动的I / O如何允许多处理?

时间:2010-07-12 18:16:27

标签: events scalability multiprocessing

我知道像select,poll,epoll等事件驱动的I / O允许某人建立一个高度可扩展的Web服务器,但我对细节感到困惑。如果只有一个执行线程和一个进程为服务器运行,那么当服务器为就绪客户端运行其“处理”例程时,这不是以串行方式完成的,以处理就绪客户端列表,因为它无法在多核或cpus上安排?此外,当这个处理发生时...服务器不会没有响应?

我曾经认为这是人们使用线程池来处理后端事件I / O的原因,但是当我最近听到不是每个人都使用线程池来处理他们的应用程序时,我很困惑。

8 个答案:

答案 0 :(得分:9)

嗯。我认为你(原始海报)和其他答案都是向后发生的。

你似乎抓住了事件驱动的部分,但是在事件发生后会发生什么事情。

要理解的关键是,Web服务器通常花费非常时间“处理”请求,并花费大量时间等待磁盘和网络I / O.

当请求进入时,服务器通常需要做两件事之一。加载文件并将其发送到客户端,或将请求传递给其他东西(通常,CGI脚本,现在FastCGI更常见,原因很明显)。

在任何一种情况下,服务器的工作都是计算最小的,它只是客户端和磁盘之间的中间人或“其他东西”。

这就是为什么这些服务器使用所谓的 非阻塞 I / O

确切的机制因操作系统而异,但关键是读取或写入请求总是立即返回(或接近足够)。例如,当您尝试写入套接字时,系统会立即接受它可以进入缓冲区的内容,或者返回类似EWOULDBLOCK错误的内容,让您知道它现在无法获取更多数据。

一旦写入被“接受”,程序就可以记录连接状态(例如“5000个10000字节发送”或其他东西)并转到下一个准备采取行动的连接,即将到来在系统准备好接收更多数据之后回到第一个。

这与普通的阻塞套接字不同,因为操作系统尝试通过网络向客户端发送数据,因此大写请求可能会阻塞很长一段时间。

从某种意义上说,这与使用线程I / O的方式没有什么不同,但它在内存,上下文切换和一般“内务管理”方面的开销大大减少,并且最大限度地利用了什么操作系统做得最好(或者应该是最好的):快速处理I / O.

对于多处理器/多核系统,适用相同的原则。这种类型的服务器在每个CPU上仍然非常高效。你只需要一个可以分叉自身的多个实例来利用额外的处理器。

答案 1 :(得分:2)

有些智慧早于多核系统的普遍可用性。在多任务环境中,这仍然是正确的。除了您的便携式电子产品,您接触的大多数机器现在都是多处理的。即使这可能也不会持续很长时间。

在纯多任务系统中,所有操作系统都会从一个作业跳到另一个作业,因为它们变为可运行(未阻止)。事件驱动和非阻塞IO在用户空间中执行相同的操作。

对于某些任务,它仍然可以辅助多处理。通过减少线程产量和互斥代码,更多的处理器可以运行应用程序更多的时钟周期。

例如,在IDE中,您不希望它不断地扫描文件系统以进行外部更改。如果你已经存在很长时间,你可能会遇到这种情况,这会让人感到烦躁/无益。它浪费资源并导致全局数据模型在更新期间锁定/不响应。设置IO事件监听器(目录上的“监视”)可以使应用程序执行其他操作,例如帮助您编写代码。

答案 2 :(得分:2)

这个想法是处理线程不必等待整个客户端对话完成才能为另一个客户端提供服务。对于许多服务器应用程序,服务器的大部分时间都花在等待IO上。即使只有一个线程处理所有请求,添加的延迟量也很小,因为服务器大部分时间都在等待IO,并且在这种安排中等待IO并不会阻止服务器响应另一个请求。这种安排并没有真正帮助服务器进行大量的CPU限制处理。

更具可伸缩性的设置​​将结合asynch IO和多个线程,理想情况下每个执行单元有一个工作线程可用,并且不会花费任何时间在IO上休眠,除非没有工作要做。

答案 3 :(得分:2)

考虑到常见的操作系统,API和典型编程语言的工作原理,您通常会有一些选择:

  • 每个客户端1个线程/进程。编程模型很简单,但不能扩展。在大多数操作系统上,在数千个线程之间切换效率低下

  • 使用一些多路复用I / O工具 - 即select / poll / epoll / etc.在unixes上,一些比其他更有效。 programmin模型更难,如果你需要处理阻塞操作作为你工作的一部分(例如调用数据库,甚至从文件系统读取文件),在某些情况下会非常困难,但它可以扩展得比1个线程服务1个客户端。

  • 混合方法,您使用多路复用IO并具有工作线程。一些处理I / O的线程,一些执行实际工作的线程,并根据您正在执行的操作调整每个线程的线程数。这是最具可扩展性的,通常也是最难编程的。

您选择的基本上是权衡。如果你已经以足够快的速度完成了某些事情,那么这并不重要。如果您不需要扩展,并且您将需要处理几十个或者非常繁忙的非繁忙客户端,使用最简单的方法是有道理的。如果您的应用程序可以在一个具有多路复用IO的线程中轻松处理当前负载的10倍,则无需解决问题并实现工作线程等。

如果你的服务器真的很忙,那么是 - 它似乎没有响应。但是CPU fast ,你可以在一秒钟内完成数百万件事。因此,如果您正在进行多路复用IO,您不会花时间等待事情,您将所有时间花在实际工作上,如果执行该工作可以在几毫秒内完成,您可以通过单个服务器为很多客户服务线。您的应用使用的OS服务,例如照顾网络IO可以自由地利用其他核心。

答案 4 :(得分:1)

  

是不是以串行方式完成处理就绪客户端列表,因为它无法在多个核心或cpus上进行调度?

事件驱动系统在事件源之间不断复用。我不确定你的串行是什么意思,但是,read()和write()不是并行运行的,如果这是你的意思,而是read()s和write()s来自/不同的客户端是混合的。

  

此外,当这个处理发生时...服务器不会没有响应吗?

将缓冲区从内核复制到用户空间或反之(或者可能不进行复制,请参阅sendfile()和splice())并不需要花费太多时间,因此它并不明显。另一方面,PHP / Perl / Python / Ruby / Java处理可能需要很长时间,但通常会卸载到另一个进程,因此它不属于主Web服务器进程/进程。

如果你真的想要高性能,典型的架构每个CPU都有一个进程/线程,每个进行事件驱动的IO,还有一些工作进程做PHP / Perl / Python / Ruby / Java / CGI / ...... / p>

修改:

一些值得深思的问题:
event driven systems and functional languages
cooperative threading a la GNU pth
more on threads vs events
SGI's state threads: pseudo-threads on top of an event-driven system
protothreads: lightweight stackless threads

答案 5 :(得分:0)

这里要记住的关键是一次只能在CPU上执行一个线程,但I / O并不总是需要CPU。当线程阻塞I / O时,CPU被释放,以便另一个线程可以执行。此外,即使在单个CPU盒上,多个线程通常也可以同时进行I / O(取决于使用的磁盘系统)。

答案 6 :(得分:0)

当事件“触发”时,会引发一个停止当前执行并执行信号处理程序代码的信号。

这个信号处理代码通常会产生一个新的线程/进程然后返回(你会看到使用进程分支而不是线程的实现)。

底线是没有多个线程你可以有并行执行的错觉,但它实际上只是停止并启动主代码然后处理信号处理程序。

Visual Basic具有类似DoEvents的功能,允许其他事件处理程序执行其操作。这通常用作主要工作(或循环的每次迭代)之前的抢占形式,以允许GUI更新(或在您的情况下,Web服务器以便开始处理客户端请求)任何其他工作。

另一种可能有用的方法是异步I / O,它将在传输完成时(或仅处理x量)引发信号,所有这些都在一个执行线程中。虽然您必须希望您使用的异步I / O库支持多核处理(或底层操作系统),以便在这种情况下获得多个内核的好处。

答案 7 :(得分:0)

生活中大多数幻想的关键是速度,想一想。多核处理器之前就存在多处理的错觉。这个想法是,如果一个处理器在进程之间切换得足够快,你就不会注意到(直到物理硬件遇到问题)。如果我们从那开始你会看到通过将它与异步I / O这样的技巧相结合,你可以模拟并行/多处理。