【问题标题】:What is best, a Single-threaded or a multi-threaded server?什么是最好的,单线程或多线程服务器?
【发布时间】:2011-05-11 08:45:37
【问题描述】:

我必须创建一个简单的客户端服务器通信以使用 C 语言 (Linux) 传输文件。

服务器接受10000端口的连接,我不知道是每个请求创建一个新线程还是创建一个固定数量的线程并使用异步技术更好。

CASE A:

client --> server --> (new thread) --> process the request

CASE B:

SERVER --> create thread 1 - thread 2 - thread 3

then

client1 --> server --> thread 1
client2 --> server --> thread 2
client3 --> server --> thread 3
client4 --> server --> thread 1
client5 --> server --> thread 2
client6 --> server --> thread 3

在这种情况下,线程 1 可以处理许多客户端的请求

我的考虑:

CASE 1:速度更快但浪费大量内存

CASE 2:速度较慢但内存不足

我错了吗?

【问题讨论】:

  • 它应该取决于客户端的数量和使用率。通常,使用固定数量的线程。但是,如果您有很多性能要求而不升级硬件,请增加线程数。我相信,需要根据预期用途实现最佳性能/内存平衡

标签: c linux network-programming


【解决方案1】:

如果您考虑检查广泛使用的 http 服务器(nginx、lighttpd、apache)的架构,您会注意到,那些使用固定线程数(所谓的“工作线程”,它们的数量应该取决于服务器上的进程或计数)是很多比使用大型线程池更快。然而,也有非常重要的时刻:

  1. “工作线程”的实现不应该像诱人的那么简单,否则不会有真正的性能提升。每个线程应该使用状态机实现每个伪并发,一次处理多个请求。此处不允许进行阻塞操作 - 例如,线程中等待硬盘驱动器 I/O 以获取文件内容的时间可用于解析下一个客户端的请求。不过,那是相当难写的代码。

  2. 在考虑性能、编码时间和代码支持时,基于线程的解决方案(具有可重复使用的线程池,因为线程创建是重量级操作)是最佳选择。如果您的服务器不应该每秒处理数千个请求,您将能够以非常自然的阻塞方式编写代码,而不会冒着性能完全失败的风险。

  3. 如您所见,“工作线程”解决方案本身仅服务于静态数据,它们将动态脚本执行代理给其他一些程序。据我所知(可能是错误的),这是由于请求的非阻塞处理的复杂性以及在其上下文中执行的一些未知的动态内容。无论如何,这对您来说应该不是问题,因为您谈到了简单的文件传输。

有限线程解决方案在重负载系统上更快的原因 - 线程http://en.wikipedia.org/wiki/Context_switch 是相当昂贵的操作,因为它需要从寄存器保存数据并加载新的数据,只要其他一些线程本地数据。如果您的线程数与进程数相比过多(例如多 1000 倍),那么您的应用程序中的大量时间将浪费在线程之间切换。

因此,对您的问题的简短回答是:“不,它与内存使用无关,选择完全取决于所服务的数据类型、计划的请求/秒计数以及花费大量时间进行编码的能力”。

【讨论】:

    【解决方案2】:

    这里没有正确的答案。取决于很多事情。而且你需要自己选择。

    “CASE 1:速度更快但浪费大量内存
    “CASE 2:速度较慢,但​​内存不足”

    错了。取决于很多事情。创建线程并没有那么昂贵(它是,但不是那么多),但是如果线程太多,你就会遇到问题。

    这在很大程度上取决于负载 - 预期负载是多少?如果是这样,可以说,每秒大约 1000 个请求 - 你知道,如果你每秒创建 1000 个线程......这将是一场灾难:D

    另外 - 创建尽可能多的线程,只要 CPU 能够处理,而无需(太多)在它们之间切换。有很大的机会(当然取决于您的程序),单核 CPU 在 10 个线程而不是 1 个(或 2 个)线程下工作的速度要慢得多。这实际上也取决于这些线程会做什么。

    我会选择创建一个线程池并重用线程。

    【讨论】:

    • @Kiril Kirov 好的。但关键是……如果我有固定数量的线程(例如:10 个线程)。最好处理每个请求的“小”部分并循环到所有请求。或者阻止这 10 个线程..完成前 10 个请求,然后继续下一个?我可以重用线程,我认为更好...但是 1 个线程 = 1 个请求(然后为其他线程重用相同的线程)或 1 个线程 = 很多请求(每次发送一小段文件?)
    • @Dail - 正如我在其他评论中所说 - “我肯定会选择第一个选项 - “同时请求 10 个,所以 10 x 时间”。没有必要阻止所有 10 个线程,只需实现(或查找已实现)管道。创建一个线程来接收请求并将它们推送到队列中。然后创建其他线程,这些线程将简单地从管道获取请求并发送它们。
    • 这些线程应该能够等待来自管道阻塞的请求(使用 select - linux.die.net/man/2/select ),否则将会有很大的 CPU 负载。
    • @Kiril Kirov 我并不完全相信.. 如果我修复了 10 个线程,并且 10 个用户下载了一个 .iso 文件 (800 mb),则线程将被阻止,直到下载终止。所以我认为最好每次发送一小段文件。这个考虑有错吗?
    • 好吧,这又取决于。这真的很主观。好的,这也很重要——这些请求的大小。当我们谈论大量数据时 - 是的,比发送较小的数据块会更好,因为让所有其他客户端永远等待是不好的。正如我一开始所说,这取决于负载。如果您期望少量客户端,下载大文件 - 您可以为每个请求创建线程。但是,如果您不这样做,那么只需创建线程池并发送较小的数据,以便能够处理所有客户端,等待一些数据。
    【解决方案3】:

    我的第一选择是使用 select(2) 进行单线程。 如果这在性能方面还不够好,我会选择线程池解决方案。 它会更好地扩展。

    有时为每个客户端创建一个线程是完全可以的。 我已经做到了,它适用于该应用程序,通常有大约 100 个客户端,最多 1000 个客户端。那是15年前的事了。今天,由于硬件更好,同一个应用程序可能可以处理 10000 个客户端。请注意,每个客户端一个线程不能很好地扩展。

    【讨论】:

      【解决方案4】:

      我知道你问这个问题已经有一段时间了,但这是我从已经用 C 编写过少量服务器的人的角度来看待你的问题。

      如果您的服务器是您自己的、完全依赖且不依赖于其他代码的,我会强烈建议您使用非阻塞单线程套接字并使用 epoll (Linux)、kqueue (BSD) 或 WSAEventSelect (Windows)。

      这可能需要您将原本“简单”的代码拆分为更小的代码块,但如果您正在关注可扩展性,这将击败任何基于线程/基于选择的服务器。

      曾经有一篇很棒的文章叫做“C10K 问题”,它完全关注如何处理 10,000 个并发连接的问题。实际上我从中学到了很多,这是它的链接:http://www.kegel.com/c10k.html

      您还可以在此处找到另一篇专注于可扩展性的精彩文章“可扩展网络”:http://bulk.fefe.de/scalable-networking.pdf

      这两本书很好读,希望对你有帮助。

      【讨论】:

        【解决方案5】:

        完全取决于您。没有正确或错误的答案。您已经确定了两者的优缺点,并且您对这两者都是正确的; 1 更快但更密集,2 更慢,因为客户端可能需要等待。

        【讨论】:

        • 好的...但我有一个疑问。如果我必须处理 100 个请求,最好有 10 个专用线程(然后“同时”处理 10 个请求,因此 10 x 时间)或 10 个公共线程然后“同时”处理所有请求,每个发送一小块文件时间?谢谢
        • @Dail - 我肯定会选择第一个选项 - “10 个请求”同时“,所以 10 x 时间”
        【解决方案6】:

        我会使用预先创建的线程池,并在它们处理完当前正在处理的请求后重新使用它们。创建线程可能很昂贵,因为它主要涉及对内核的调用。

        有“线程池”类型的项目here 使用pthreads。也许你可以从那里得到一些关于如何实施的想法。

        【讨论】:

          【解决方案7】:

          真的取决于你的服务器在做什么。

          我建议您尽可能做最简单的事情。这可能是一个单进程模型,它使用 select、poll、libevent 或类似方法复用所有可用的连接。

          也就是说,如果您使用的是 TCP。

          如果您使用 UDP,则更容易,因为应用程序可以使用一个套接字完成所有操作,因此它可以(可能)使用阻塞套接字。

          【讨论】:

            猜你喜欢
            • 2013-09-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-29
            • 2013-01-28
            • 2012-09-09
            • 2013-11-15
            • 2023-03-04
            相关资源
            最近更新 更多