【问题标题】:C# Database Blocking Threads bad for large amounts of clients [closed]C#数据库阻塞线程对大量客户端不利[关闭]
【发布时间】:2017-01-26 19:24:22
【问题描述】:

信息:

我为企业创建了一个 C# 控制台服务器和 WinForms 客户端。

(如果我错了,请纠正我,我已经做了很多研究。所以我可能混淆了某些主题。)

在服务器上,我读到使用Socket.BegingReceiveAsyncCallback 方法是最好的。由于它使用ThreadPool 类。对客户端进行多线程处理,同时重用线程等。(而不是在单个线程设置或每个客户端一个线程上执行 while 循环。(我已经读到这些具有我不感兴趣的特定缺点。))

基本上,我努力的是确保如果我正在制作的应用程序突然爆炸并且有 100,000(甚至可能 1,000,000?!)人们同时使用它,它将为每个客户端保持良好和稳定的连接.


情况:

服务器: Intel i5 4 Core:服务器在另一台服务器上处理 MYSQL 请求,并将其以他们期望的方式格式化返回给客户端。

客户端:无限的硬件资源:浏览表单时向服务器询问数据以填写表单。

所以我担心: 假设有 4 个客户端连接并请求服务器处理数据,这大约需要 5 秒来处理 MYSQL 数据库(我知道,处理速度就像一生。 (最坏的情况等))。如果第五个客户端连接怎么办。这意味着 ThreadPool 必须创建另一个线程来保存这个进程。现在使这些数字更大,我有 100 个同时客户端请求确切的长时间运行方法。在 4 核 CPU 上。因此,现在 ThreadPool 花费了十多秒或更长时间来创建所有这些新线程。


问题:

编辑:缩小问题范围:

我如何为 FindPrimeNumbers 调用实现异步编程。 (该方法是在数据库(不同服务器上的 MySQL,如果有帮助的话)搜索数字。)

我该怎么做才能防止这种情况发生?我如何使它更直观。如何防止大负载可能造成不良的用户体验等。


须藤代码

FindPrimerNumber 方法类似于数据库调用。

class Program
{
    private static Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    private static List<Socket> clientSockets = new List<Socket>();
    private static CancellationTokenSource CancelSource = new CancellationTokenSource();
    private static CancellationToken CancelToken = CancelSource.Token;
    private const int BUFFER_SIZE = 2048;
    private const int PORT = 100;
    private static readonly byte[] buffer = new byte[BUFFER_SIZE];

    static void Main()
    {
        SetupServer();
        Console.ReadLine();
        CloseAllSockets();
    }

    private static void SetupServer()
    {
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT));
        serverSocket.Listen(0);
        serverSocket.BeginAccept(BeginAcceptCallback, null);
    }

    private static void CloseAllSockets()
    {
        foreach (Socket socket in clientSockets)
        {
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }

        serverSocket.Close();
    }

    private static void BeginAcceptCallback(IAsyncResult AR)
    {
        Socket socket = serverSocket.EndAccept(AR);
        clientSockets.Add(socket);
        socket.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, BeginReceiveCallback, socket);
        Console.WriteLine("Client connected!");
        serverSocket.BeginAccept(BeginAcceptCallback, null);
    }

    private static void BeginReceiveCallback(IAsyncResult AR)
    {
        Socket current = (Socket)AR.AsyncState;
        int receivedInt = current.EndReceive(AR);
        byte[] received = new byte[receivedInt];
        Array.Copy(buffer, received, receivedInt);

        current.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, BeginReceiveCallback, current);

        string text = Encoding.ASCII.GetString(received);
        int number = 0;
        if (text.ToLower() == "exit")
        {
            current.Shutdown(SocketShutdown.Both);
            current.Close();
            clientSockets.Remove(current);
            Console.WriteLine("Client Disconnected");
            return;
        }
        else if (int.TryParse(text, out number))
        {
            int num = FindPrimeNumber(number);
            byte[] data = Encoding.ASCII.GetBytes(num.ToString());
            current.Send(data);
            Console.WriteLine("Prime Number: "+num+" :Sent");
        }
        else
        {
            current.Send(Encoding.ASCII.GetBytes("Not a Command"));
        }
    }

    //VERY LONG CPU FUNCTION
    public static int FindPrimeNumber(int n)
    {
        //Connect to database (MySQL Database on a different server, if that makes a difference)
        //Send Query
        //Read Return
        return queryReturn()
    }
}

【问题讨论】:

  • 只要让你的 SQL 请求异步。此外,您误解了线程池中的线程如何管理代码执行。
  • 并非所有东西都绑定到 CPU,几乎总是在通信中中继的主要原因,这不需要太多 CPU 时间,所以,只要所有东西都异步并且不等待同步响应 - 一切都很好,并且你只需要担心你的硬件。
  • 是的,可扩展到大量用户的网络服务器的最大敌人是阻塞线程。任何阻塞代码都应替换为 async/await 等效代码。 W.R.T.使用 APM BeginXXXEndXXX 对,您还应该考虑转向 TAP API(即支持 async/await 的基于任务的 API)。您的代码会更简洁,并且它们与您在帖子中提到的 APM API 具有或多或少相同的性能特征,并且具有显着减少认知负担的优势。
  • ...所以任何时候你有阻塞的代码,例如同步 IO、Thread.Sleep、等待信号量等,这些都应该替换为 async await 基于任务的等效项。跨度>
  • 如果您绝对热衷于避免阻塞代码,那么您的 ThreadPool 确实没有理由需要多个线程。在典型的 CRUD 应用程序上,CPU 几乎处于空闲状态,我已经为数千个并发客户端提供服务。它很少需要启动超过默认线程数。

标签: c# mysql multithreading sockets threadpool


【解决方案1】:

您有一个线程正在运行这一事实并不意味着该线程必须在内核上消耗 100% 的时钟周期。启动一个诊断工具(进程查看器或其他东西)并查看您机器上的线程数:它将有数百个。然后查看总 CPU 消耗:它将接近于零。绝大多数线程将大部分时间都花在等待其他事情发生时什么都不做。

另一方面,如果您在数据库服务器上执行的查询需要长达 5 秒的时间,那么您就有麻烦了,因为这意味着您对其施加了太大的压力,因此无论并行化如何,整个系统都会受到影响。

这些都是非常基本的东西,所以如果你还没有掌握它,那么担心它是不合时宜和不切实际的。只做你必须做的事,以后再担心性能。

【讨论】:

  • 非常正确。但是您所描述的听起来像是将每个客户放在自己的线程上。如果这个数字上升到数十万,我听说可能会出现问题。
  • 从网上的各种文章和资料来看,如果我使用ThreadPool风格似乎更好。为了重用线程等。
  • 是的,当然你会使用线程池。我不建议将每个客户放在自己的线程上。但是如果很多请求同时到达,那么就会从线程池中拉出那么多线程。这 still 并不意味着这些线程将消耗任何 CPU。他们大多会等待。例如,如果一个线程发出一个需要 5 秒才能完成的数据库请求,那么该线程将等待 5 秒。忘掉线程吧,你的问题是 5 秒的数据库请求会对数据库造成负担。
  • 线程池的意义在于给程序员一种拥有无限线程的错觉,同时只产生实际需要的少量线程。线程的意义在于给程序员一种顺序指令执行的错觉,而大多数时候没有执行,线程只是在等待。所以,这一切都只是为了我们的方便。
  • Async-await 是一个巨大的改进,它保持了顺序执行的错觉,同时不保持长时间运行的线程处于活动状态:只有在等待完成后才会从池中获取线程,(为了运行 await 之后的代码,) 并在遇到下一个 await 时立即将线程返回到池中。
猜你喜欢
  • 2015-05-22
  • 1970-01-01
  • 2015-07-04
  • 2012-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-29
  • 1970-01-01
相关资源
最近更新 更多