11

我正在编写一个客户端模拟程序,其中所有模拟客户端针对服务器运行一些预定义的例程 - 这是一个在 azure 中运行的具有四个实例的 Web 服务器。

所有模拟客户端在连接到服务器后运行相同的例程。

在任何时候,我都想使用我的程序模拟 300 到 800 个客户。

我的问题是:我应该创建 N 个客户端类实例并在 N 个不同的线程中运行它们吗?或者

我应该使用任务库来做这些事情吗?

4

4 回答 4

23

您当然不应该创建 800 个线程。

让我们退后一步。您有一个称为“服务器”的设备,它接收来自“客户端”的“请求”并将“响应”发送回这些客户端。假设请求是邮局投递的纸张,响应是包含书籍的盒子,也是邮局投递的。

您希望模拟800 个客户端以测试服务器。

假设线程是人,处理器是椅子。一个人只能坐在椅子上工作。

创建 800 个线程相当于出去雇佣 800 人,然后付钱给每个人发一封信给服务器。但是你只有四把椅子,所以这 800 个人必须轮流使用椅子。

这在现实生活中将是一个可笑的解决方案。线程,就像人一样,非常昂贵。您应该尽量减少创建的线程数。

那么,您是否应该改为通过任务工厂创建 800 个任务并让 TPL 为您并行化它们?

不,你也不应该那样做。TPL 有一群人(线程)可供借鉴,它试图安排事情,使工资单上的人不超过可供他们坐的椅子。但你的任务不是“受椅子限制” - - 人们将坐在椅子上,将请求发送到服务器,然后在等待响应返回时离开椅子。在他们等待的同时,TPL 现在不得不雇佣更多的人来服务额外的任务。

访问 Web 服务器是 I/O 绑定的;您应该只为受 CPU 限制的任务创建线程池任务。

正确的解决方案是雇用两个人。

一个人——“I/O 完成线程”——除了将请求放入邮箱并检查传入的包之外什么都不做。另一个人——“模拟”人——计算出模拟 800 个客户的正确“时间表”。模拟人制定时间表,然后睡觉。当需要向服务器发送另一个请求时,她会醒来。当她醒来时,她告诉 I/O 完成线程将这封信放入邮箱中,并在收到响应时唤醒她。然后她再次进入睡眠状态,直到该发送另一个请求或响应的时间了进来,需要验证。

您应该做的是(1)获取 C# 5 的 beta 版本并用于async/await创建向服务器发送请求的任务,然后将控制权交还给消息循环,直到该发送另一个请求或响应来了或者,如果您不想使用 C# 5,您应该创建一个任务完成源,并设置具有正确延续的任务。

简而言之:处理许多并行 I/O 任务的正确方法是创建非常少量的线程,每个线程一次只做非常少量的工作。让 I/O 完成线程处理 I/O 的细节。你不需要雇佣 800 人来模拟发送 800 封信。两个人,一个看信箱,一个写信。

于 2012-04-05T19:13:51.847 回答
2

在这种情况下,答案并不是那么简单。这实际上取决于您希望如何模拟您的客户:

  1. 如果您想连接 800 个客户端,但不一定同时连接,那么使用Tasks 是个好主意。它们是轻量级的,并且可以有效地利用底层ThreadPool.

  2. 如果您真的希望客户端完全并行,恐怕没有办法真正避免线程。没有什么神奇的方法可以同时执行 800 个轻量级任务。抽象是轻量级的Task,正是因为它使用了线程池。这意味着许多任务被映射到少量的实际线程。但是,当然,这意味着它们并不是真正并行运行,而是尽可能安排运行。的最大线程数为 250(AFAIK),因此如果您使用s ThreadPool,一次实际执行的“客户端”不会超过 250 个。Task解决方案是将最大线程数设置为 800,但此时它与使用经典线程相同。

于 2012-04-05T18:51:49.783 回答
1

我会使用任务库,让任务库为您处理所有线程。您不想启动 800 个线程。一次同时运行这么多线程是个坏主意,这是另一个讨论这个问题的堆栈溢出问题:.NET 应用程序中的最大线程数?

于 2012-04-05T18:12:35.577 回答
1

对于这个应用程序域是你最好的选择。

应用程序域是 .NET 应用程序在其中执行的运行时隔离单元。它提供托管内存边界、应用程序配置设置容器以及为分布式应用程序提供通信接口。

每个 .NET 应用程序通常只承载一个应用程序域,该域由 CLR 在给定进程/程序启动时自动创建。在单个进程/程序中创建其他应用程序域有时很有用(在您的情况下)。使用多个应用程序域可以避免通信复杂性,并使用多个单独的进程出现,并提供任务隔离。

对于你想要的,你有两个选择。

  1. 在同一域中的单独线程上启动 X 线程。

这将意味着您将不得不非常厌倦线程安全,这对于模拟多个登录、模拟客户端等任务将非常困难。

  1. 在同一进程中启动 X 个线程,每个线程在其自己的应用程序域中。

这将使每个旋转线程保持隔离,并且托管应用程序/程序也易于访问。通过让你们所有人在 X 个单独的应用程序域中进行 X 模拟,每个域将被隔离并且无法通过静态类成员等干扰另一个客户端模拟。

以下是 Joseph Albahari 的书C# 4.0 In a Nutshell的摘录的帮助,我强烈建议您获取:

40 个并发客户端模拟的示例可能对您有用:

class program
{
    static void main()
    {
        // Create 40 domains and 40 threads.
        AppDomain[] domains = new AppDomain[40];
        Thread[] thread = new Thread[40];

        for (int i = 0; i < 40; i++)
        {
            domains[i] = AppDomain.CreateDomain("Client Simulation " + i);
            thread[i] = new Thread(SimulateClientInOtherDomain);
        }

        // Start all threads, passing to each thread its app domain.
        for (int j = 0; j < 40; j++)
            threads[j].Start(domains[j]);

        // Wait for the threads to finish.
        for (int k = 0; k < 40; k++)
            threads[k].Join();

        // Unload the application domains.
        for (int l = 0; l < 40; l++)
            AppDomain.Unload(domains[l]);
    }

    // Thread start with input of with domain to run on/in.
    static void SimulateClientInOtherDomain(object domain)
    {
        ((AppDomain)domain).DoCallBack(Simulate);
    }

    static void Simulate()
    {
       Client simClient1 = new Client("Bill", "Gates", ...);
       simClient1.Simulate();
    }
}

我希望这有帮助。

于 2012-04-05T18:56:56.953 回答