简体   繁体   English

为什么Thread.Sleep()是如此CPU密集型?

[英]Why Thread.Sleep() is so CPU intensive?

I have an ASP.NET page with this pseduo code: 我有一个带有这个pseduo代码的ASP.NET页面:

while (read)
{
   Response.OutputStream.Write(buffer, 0, buffer.Length);
   Response.Flush();
}

Any client who requests this page will start to download a binary file. 请求此页面的任何客户端都将开始下载二进制文件。 Everything is OK at this point but clients had no limit in download speed so changed the above code to this: 此时一切正常,但客户端的下载速度没有限制,因此将上述代码更改为:

while (read)
{
   Response.OutputStream.Write(buffer, 0, buffer.Length);
   Response.Flush();
   Thread.Sleep(500);
}

Speed problem is solved now, but under test with 100 concurrent clients who connect one after another (3 seconds lag between each new connection) the CPU usage increases when the number of clients increases and when there are 70 ~ 80 concurrent clients CPU reaches 100% and any new connection is refused. 速度问题现在已经解决,但是在测试中有100个并发客户端一个接一个地连接(每个新连接之间延迟3秒),当客户端数量增加时CPU使用率增加,当70到80个并发客户端CPU达到100%时CPU使用率增加并且任何新连接都被拒绝。 Numbers may be different on other machines but the question is why Thread.Sleep() is so CPU intensive and is there any way to speed done the client without CPU rising ? 其他机器上的数字可能不同,但问题是为什么Thread.Sleep()是如此CPU密集型,有没有办法加速客户端没有CPU上升?

I can do it at IIS level but I need more control from inside of my application. 我可以在IIS级别执行此操作,但我需要从应用程序内部进行更多控制。

Let's take a look at whether Michael's answer seems reasonable. 让我们来看看迈克尔的答案是否合理。

Now, Michael wisely points out that Thread.Sleep(500) shouldn't cost much in the way of CPU. 现在,迈克尔明智地指出Thread.Sleep(500)不应该在CPU方面花费太多。 That's all well and good in theory, but let's see if that pans out in practice. 这在理论上一切都很好,但让我们看看是否在实践中实现了这一点。

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            Thread.Sleep(500);
        }
    }

Running this, the CPU use of the application hovers around the 0% mark. 运行它,应用程序的CPU使用率徘徊在0%左右。

Michael also points out that since all the threads that ASP.NET has to use are sleeping, it will have to spawn new threads, and offers that this is expensive. 迈克尔还指出,由于ASP.NET必须使用的所有线程都处于休眠状态,因此必须生成新线程,并提供这样做很昂贵。 Let's try not sleeping, but doing lots of spawning: 让我们试着不要睡觉,但要做很多产卵:

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            new Thread(o => {}).Start();
        }
    }

We create lots of threads, but they just execute a null operation. 我们创建了很多线程,但它们只执行一个null操作。 That uses a lot of CPU, even though the threads aren't doing anything. 这使用了大量的CPU,即使线程没有做任何事情。

The total number of threads never gets very high though, because each lives for such a short time. 但是线程的总数永远不会很高,因为每个线程都存在这么短的时间。 Lets combine the two: 让两者结合起来:

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            new Thread(o => {Thread.Sleep(500);}).Start();
        }
    }

Adding this operation that we have shown to be low in CPU use to each thread increases CPU use even more, as the threads mount up. 将这个我们已经证明CPU使用率低的操作添加到每个线程会增加CPU的使用量,因为线程会增加。 If I run it in a debugger it pushes up to near 100% CPU. 如果我在调试器中运行它,它会推高到接近100%的CPU。 If I run it outside of a debugger, it performs a bit better, but only because it throws an out of memory exception before it gets a chance to hit 100%. 如果我在调试器之外运行它,它会更好一点,但只是因为它在有机会达到100%之前抛出了内存不足的异常。

So, it isn't Thread.Sleep itself that is the problem, but the side-effect that having all available threads sleep forces more and more threads to be created to handle other work, just as Michael said. 因此,问题不在于Thread.Sleep本身,但是让所有可用线程睡眠的副作用迫使越来越多的线程被创建来处理其他工作,就像迈克尔所说的那样。

Just a guess: 只是一个猜测:

I don't think it's Thread.Sleep() that's tying up the CPU - it's the fact that you're causing threads to be tied up responding to a request for so long, and the system needs to spin up new threads (and other resources) to respond to new requests since those sleeping threads are no longer available in the thread pool. 我不认为是Thread.Sleep()会占用CPU - 这是因为你导致线程被绑定响应请求这么久,系统需要启动新线程(和其他资源)响应新请求,因为线程池中不再提供这些休眠线程。

Rather than an ASP.NET page you should implement an IHttpAsyncHandler . 您应该实现IHttpAsyncHandler,而不是ASP.NET页面。 ASP.NET page code puts many things between your code and the browser that would not be appropriate for transferring binary files. ASP.NET页面代码在您的代码和浏览器之间放置了许多不适合传输二进制文件的东西。 Also, since you're attempting to perform rate limitation, you should use asynchronous code to limit resource usage, which would be difficult in an ASP.NET page. 此外,由于您尝试执行速率限制,因此应使用异步代码来限制资源使用,这在ASP.NET页面中很难实现。 Creating an IHttpAsyncHandler is fairly simple. 创建IHttpAsyncHandler非常简单。 Just trigger some asynchronous operations in the BeginProcessRequest method, and don't forget to properly close the context to show you have reached the end of the file. 只需在BeginProcessRequest方法中触发一些异步操作,并且不要忘记正确关闭上下文以显示已到达文件末尾。 IIS won't be able to close it for you here. IIS将无法在此处为您关闭它。

The following is my really bad example of how to perform an an asynchronous operation consisting of a series of steps, counting from 0 to 10, each performed at a 500ms interval. 以下是我如何执行由一系列步骤组成的异步操作的非常糟糕的例子,从0到10计数,每个步骤以500ms的间隔执行。

using System;
using System.Threading;

namespace ConsoleApplication1 {
    class Program {
        static void Main() {
            // Create IO instances
            EventWaitHandle WaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); // We don't actually fire this event, just need a ref
            EventWaitHandle StopWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
            int Counter = 0;
            WaitOrTimerCallback AsyncIOMethod = (s, t) => { };
            AsyncIOMethod = (s, t) => {
                // Handle IO step
                Counter++;
                Console.WriteLine(Counter);
                if (Counter >= 10)
                    // Counter has reaced 10 so we stop
                    StopWaitHandle.Set();
                else
                    // Register the next step in the thread pool
                    ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true);
            };

            // Do initial IO
            Console.WriteLine(Counter);
            // Register the first step in the thread pool
            ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true);
            // We force the main thread to wait here so that the demo doesn't close instantly
            StopWaitHandle.WaitOne();
        }
    }
}

You'll also need to register your IHttpAsyncHandler implementation with IIS in whichever way is appropriate for your situation. 您还需要以适合您情况的方式向IIS注册IHttpAsyncHandler实现。

Its because the thread gets a priority boost every time it yields its time slice. 这是因为线程每次产生时间片时都会获得优先级提升。 Avoid calling sleep often ( particularly with low values ). 避免经常打电话给睡眠(特别是低值)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM