【问题标题】:C# - When to use standard threads, ThreadPool, and TPL in a high-activity serverC# - 何时在高活动服务器中使用标准线程、ThreadPool 和 TPL
【发布时间】:2011-03-13 09:39:40
【问题描述】:

我最近阅读了很多关于线程的文章,因为我正在寻求开发一个高性能、可扩展的 TCP 服务器,能够处理多达 10,000-20,000 个客户端,其中每个客户端始终与服务器进行双向通信基于命令的系统。服务器将接收命令,并根据命令执行单个(或多个)任务。我的问题是如何在各种情况下适当地使用 .NET 线程构造,执行可能需要一分钟到几个小时的任务,具体取决于正在执行的工作。

最让我困惑的是,在我阅读的任何地方,我都会看到类似“使用手动创建的线程(或自定义线程池)来处理‘长时间运行’的任务,并使用 TPL 来处理短期任务,或需要并行处理的任务。”究竟什么是 一项长期运行的任务?那是 5 秒、60 秒还是一个小时?

我应该在什么时间范围内使用这三种创建线程的方法:

  • 手动创建的线程
  • .NET 线程池类
  • TPL

我考虑过的另一个问题如下——假设我的服务器实际上连接了 20,000 个客户端,每个客户端每秒发送 1 个命令(可以转换为一个或多个任务)。即使拥有强大的硬件,我是否有可能将过高的工作负载推入我拥有的任何线程池/工作项队列,从而最终在队列慢慢填满到最大值后生成 OutOfMemoryException?

任何见解将不胜感激。

【问题讨论】:

  • 平均而言,处理每个命令需要多长时间?
  • 就是这样——真的不知道命令需要多长时间才能完成。我觉得我已经收到了足够的信息来制定相应的计划。当然,20,000 个客户是一个很长的目标(5,000 个更像它),但如果有必要,我希望在未来做好扩大规模的准备。谢谢大家的回复。
  • @slashp 长时间运行意味着“超过几百毫秒”,即。会破坏您的其他工作(并为处理带来显着延迟)的东西。例如,如果您的目标是 5 毫秒延迟,即使 1 毫秒也可以被认为是“长时间运行”:D 另外,我想指出 20k TCP 客户端正在接近实际限制 - 每个 TCP 连接都需要一个单独的端口,而你只有 65535 个 maximum - 而且它们在关闭后可以存活大约 4 分钟。您可能不得不考虑横向扩展(更多服务器)而不是纵向扩展(每台服务器更多连接)。

标签: c# multithreading c#-4.0 threadpool


【解决方案1】:

Marcs 的建议是我的做法。但是,如果您的任务花费的时间超过一秒并且客户端每秒发送一个请求,则队列会稳步增加。

在这种情况下,我将使用一台服务器作为外观,它从客户端获取所有请求并以异步方式将响应发送回它们。

服务器会将所有请求放入一个消息队列中,由其他几个服务器读取。这些服务器处理请求并将响应放入由第一台服务器读取的另一个消息队列中。

另一种解决方案是使用负载平衡服务器。

【讨论】:

    【解决方案2】:

    实际上,对于那种情况,所有都是次要的;您应该首先查看的是 asyc-IO,也就是 .BeginRead(...) 等;这允许您通过等待 IO 完成端口来最小化线程数 - 效率更高。

    一旦你有一个完整的消息,在那个规模我会把消息扔到一个自定义线程池/同步队列中。我将有一个受控数量的 常规 线程(不是池线程或 IOCP)服务于该队列以处理每个项目。

    碰巧我现在正在做类似的事情(小规模);为了防止内存爆炸,我限制了工作队列;如果它已满(即工作人员无法跟上),那么您可能会阻塞 IOCP 一小会儿,也许最终会超时,这会告诉客户端在 IOCP 层“太忙”。

    【讨论】:

    • +1 表示“队列 + 服务线程” - 很可能是最好的方法
    • 对不起,我忘了说我知道 IOCP 将用于网络部分,我只是想提供一些我想要完成的背景。我也明白在使用 IOCP 时我需要小心堆碎片,这也可能导致 OutOfMemoryException。我真的只需要澄清何时使用这三个构造中的哪一个,以及“长期运行”任务的定义。
    • @slashp 我编辑了一些关于内存问题的想法;由于您可能会不断运行,因此您最好自己拥有线程 - 避免与 ThreadPool 混淆,另外您可以命名它们
    【解决方案3】:

    最让我困惑的是 事实上,无论我在哪里阅读,我都能看到 类似“使用手动创建的 线程(或自定义线程池)到 处理“长时间运行”的任务,并使用 用于短期任务或任务的 TPL 需要并行处理。”

    奇怪的建议,或者您可能引用错误了一点。线程也能够进行并行处理,使用 TPL,您可以使用 LongRunning 选项创建任务。剩下的就是你不应该在 ThreadPool 上启动长任务。

    究竟什么是长期运行的任务? 那是 5 秒,60 秒,一个 小时?

    TPL 在 ThreadPool 之上运行,TP 将以每秒最多 2 个的速度创建新线程。所以长时间运行是> = 500 ms


    即使有强大的硬件,也不是 我有机会推动 工作量太大 我有线程池/工作项队列,

    是的,没有线程工具可以扩展您的实际容量......

    如果有 20k 个客户端,您可能需要一个服务器场,这是一个可以尽早包含在您的设计中的选项...

    因此,在深入研究套接字之前,您可能应该好好看看 WCF。

    【讨论】:

    • 现在我认为线程调度程序不会总是尊重“LongRunning”选项?感谢您提供有关“长期运行”的信息。
    • 尽管 LongRunning 被定义为提示,但默认 TaskScheduler 中的实际实现将始终创建一个新线程 - 请参阅 coderkarl.wordpress.com/2012/12/13/…
    • @HenkHolterman 你怎么知道 ThreadPool 以每秒最多 2 个的速度创建新线程?哪里可以配置?
    【解决方案4】:

    您似乎正在构建一个服务器,它将为数千个并发请求提供服务,每个请求的运行时间从几分钟到几小时。

    通常,使线程工作负载足够短,最多可以在几秒钟内完成。再长一点,您将开始占用服务器资源并严重影响服务器的可扩展性。数以万计的线程阻塞长时间运行的操作,或者同时执行这些长时间运行的操作,肯定会破坏您的可伸缩性。

    不确定每次长时间运行会消耗多少 CPU 时间。这会影响您的设计,例如:

    如果每个长时间运行主要阻塞 I/O,您可以使用一个线程等待重叠的 I/O 或 I/O 完成端口,然后唤醒新线程来处理已完成的 I/O(最多节流限制)。您需要有一定数量的线程来为等待的连接提供服务。

    如果每个长时间运行的操作都等待其他操作完成,请考虑使用 Windows Workflow Foundation。

    如果每个长时间运行的操作都消耗 CPU,那么您不希望在任何时候运行太多的操作,否则会破坏您的服务器。在这种情况下,请使用 MSMQ 和/或 TPL 对任务进行排队,并确保只有少数几个任务同时运行。

    在所有这些中,您似乎都在保持客户端连接打开。最糟糕的事情是为每个连接保留一个线程阻塞。您需要实施线程池策略,以便仅使用有限数量的线程来服务所有未完成的连接。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多