【问题标题】:How is a single threaded server able to cater to multiple clients even thru non-blocking I/O?单线程服务器如何通过非阻塞 I/O 满足多个客户端的需求?
【发布时间】:2021-09-18 03:03:53
【问题描述】:

在实现服务器时,我们可以将一个客户端请求委托给一个线程。我读到这种方法的问题是每个线程都有自己的堆栈,这将非常“昂贵”。另一种方法是让服务器成为单线程并在这个服务器线程上实现所有客户端请求,并将 I/O 请求作为非阻塞请求。我的疑问是,如果一个服务器线程同时运行多个客户端请求,服务器代码不会有指令指针、一组局部变量、每个客户端请求的函数调用堆栈,那么这不会像以前一样“昂贵”。我们如何真正节省?

【问题讨论】:

  • 单个服务器线程将只有一个指令指针和一组局部变量。但是,是的,必须记住每个客户端的状态,现在必须使用可以与特定客户端关联的堆对象来完成。这是否真的会节省资源,很大程度上取决于底层系统以及它将分配给每个线程的原生资源类型。

标签: java multithreading performance server nonblocking


【解决方案1】:

我读到这种方法的问题是每个线程都有自己的堆栈,这将非常“昂贵”。

取决于您的系统资源的紧张程度。在许多当前架构上,每个线程分配的典型 JVM 堆栈空间默认为 1mB,尽管可以使用 -Xss 命令行参数进行调整。你的 JVM 有多少系统内存可供使用,你需要多少线程决定了你是否愿意为编写服务器单线程付出高昂的代价。

我的疑问是,如果一个服务器线程同时运行多个客户端请求,服务器代码不会有指令指针、一组局部变量、每个客户端请求的函数调用堆栈,那么这不会再次“昂贵”和以前一样

它肯定需要在堆中存储每个请求的上下文信息,但我怀疑保存为传入连接提供服务所需的变量的信息价值将远小于 1mB。

像大多数事情一样,当我们寻求优化程序时,无论是减少内存还是其他系统资源使用,我们真正竞争的是代码复杂性。变得正确更难,维护也更难。 尽管线程程序可能非常复杂,但将请求处理程序隔离在单个线程中可以使代码非常简单,除非它需要以某种方式与其他请求进行协调。在大多数情况下,编写高性能单线程服务器会比线程版本复杂得多。当然,由于您不能使用多个处理器,因此性能也会受到限制。

【讨论】:

    【解决方案2】:

    使用非阻塞 I/O,单个 I/O 线程可以处理多个连接。 I/O 线程将在以下情况下收到通知:

    • 客户端想要连接
    • 上一轮套接字的写缓冲区满时,连接的套接字的写缓冲区有空间。
    • 连接的套接字的读取缓冲区有数据可供读取

    因此线程利用事件多路复用来使用选择器同时为连接提供服务。一个线程等待来自选择器的一组选择键,选择键包含您注册的事件的状态,您可以将用户数据(如“会话”)附加到选择键。

    这里使用的一个非常典型的设计模式是反应器模式。

    但您通常希望防止因运行较长的请求而阻塞 I/O 线程。因此,您将工作卸载到不同的线程池。然后反应器变为前摄器模式。

    您通常希望扩展 I/O 线程的数量。所以你可以有一堆并行的 I/O 线程。

    但您的应用程序中的线程总数应该保持有限。

    这一切都取决于你想要什么。以上是我在为 Hazelcast 工作时经常使用的技术。

    我不会从头开始编写所有这些逻辑。如果你想利用网络,我会看看 Netty。它负责大部分繁重的工作,并内置了各种优化。

    我不能 100% 确定不写入其堆栈的线程是否真的会消耗 1MB 的物理内存。在 Linux 中,(共享)零页用于内存分配,因此除非实际写入线程的堆栈,否则不会分配实际的页框(物理内存);这将在写入时触发副本以执行页框的实际分配。除了节省内存之外,这还可以防止在将堆栈归零时浪费内存带宽。线程的内存消耗是一回事;但上下文切换是另一个问题。如果线程数多于内核数,上下文切换可能会成为真正的性能问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-20
      • 1970-01-01
      • 2011-06-15
      • 1970-01-01
      • 2019-10-30
      • 1970-01-01
      • 2015-05-22
      • 2017-10-09
      相关资源
      最近更新 更多