【问题标题】:Threading and scaling model for TCP server with epoll带有 epoll 的 TCP 服务器的线程和缩放模型
【发布时间】:2012-01-08 14:34:20
【问题描述】:

我已经阅读了C10K 文档以及许多有关扩展套接字服务器的相关论文。所有的道路都指向以下:

  1. 避免“每个连接线程”的经典错误。

  2. 首选 epoll 而不是 select。

  3. 同样,unix 中的传统异步 io 机制可能难以使用。

我的简单 TCP 服务器仅在专用端口上的侦听套接字上侦听客户端连接。收到新连接后,解析请求,然后发回响应。然后优雅地关闭套接字。

我认为我对如何使用 epoll 在单个线程上扩展它有很好的了解。只有一个循环调用 epoll_wait 用于侦听套接字以及现有的客户端连接。返回后,代码将根据刚刚收到信号的套接字来处理新的创建新客户端连接以及管理现有连接的状态。也许还有一些逻辑来管理连接超时、套接字的优雅关闭以及每个连接的有效资源分配。看起来很简单。

但是如果我想扩展它以利用多个线程和多个 cpu 内核呢? 我想到的核心思想是:

一个专用线程,用于侦听 TCP 侦听套接字上的传入连接。然后是一组 N 个线程(或线程池)来处理所有活动的并发客户端连接。然后发明一些线程安全的方式,让监听线程将新连接(套接字)“分派”到可用的工作线程之一。 (Windows 中的 IOCP)。工作线程将在它正在处理的所有连接上使用 epoll 循环来执行单线程方法会执行的操作。

我在正确的轨道上吗?或者是否有一个标准的设计模式可以在多个线程上使用 epoll 来做一个 TCP 服务器?

关于监听线程如何将新连接分派到线程池的建议?

【问题讨论】:

  • 如果您选择的语言很灵活,您可能想尝试vibed.org,它抽象了异步编程的异步特性,因此您仍然可以以同步方式进行编程。例如ubyte[] buf = 新 ubyte[](1024);自动数据 = conn.read(buf); conn.write(data);

标签: linux sockets tcp epoll


【解决方案1】:
  1. 首先,请注意它是 C*10K*。如果您不到 100 岁(在典型系统上),请不要担心。即便如此,这也取决于您的套接字在做什么。
  2. 是的,但请记住,epoll 操作需要系统调用,并且它们的成本可能会或可能不会比您自己管理几个 fd_sets 的成本更昂贵。 poll 也是如此。在低计数时,每次迭代在用户空间进行处理会更便宜。
  3. 当您不受限于可以根据需要处理的几个套接字时,异步 IO 非常痛苦。大多数人通过使用事件循环来应对,但这会破坏和反转您的程序流程。它通常还需要使用大型、笨重的框架来实现此目的,因为可靠且快速的事件循环并不容易做到正确。

第一个问题是,你需要这个吗?如果您通过产生线程来处理每个传入请求来轻松应对现有流量,那么请继续这样做。代码会更简单,你所有的库都会很好地运行。

如上所述,同时处理并发请求可能很复杂。如果您想在单个循环中执行此操作,则还需要在生成响应时保证 CPU 不足。

如果您的响应生成成本很高,那么您提出的调度模型是典型的第一步解决方案。您可以分叉或使用线程。在选择池机制时不应考虑分叉或生成线程的成本:您应该使用这种机制来限制或排序系统上的负载。

将套接字批处理到多个 epoll 循环是多余的。如果您如此绝望,请使用多个进程。请注意,可以通过多个线程和进程在套接字上accept

【讨论】:

  • Matt,其实我还没有写 TCP 网络核心。因此,如果首先要考虑更好的设计模式,我显然没有理由从“每个连接的线程”模型开始。是说“select”比 epoll 低套接字数便宜吗?您能否详细说明“cpu 饥饿”问题?我同意负载平衡设计点。而且我已经考虑了多个线程都在接受时阻塞。
【解决方案2】:

我猜你是在正确的轨道上。但我也认为细节取决于特定情况(带宽、请求模式、个人请求处理等)。我认为您应该尝试并仔细进行基准测试。

【讨论】:

    猜你喜欢
    • 2012-01-07
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-27
    • 2018-01-23
    相关资源
    最近更新 更多