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

时间:2010-10-07 21:58:39

标签: c# asp.net multithreading performance

我有一个带有这个pseduo代码的ASP.NET页面:

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

任何请求此页面的客户端都将开始下载二进制文件。此时一切正常,但客户端的下载速度没有限制,因此将上述代码更改为:

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

速度问题现在已经解决,但是在100个并发客户端接连测试(每个新连接之间延迟3秒)的情况下测试,当客户端数量增加和70~80个并发客户端CPU时CPU使用率增加达到100%并且拒绝任何新连接。其他机器上的数字可能不同,但问题是为什么Thread.Sleep()是如此CPU密集型,有没有办法加速完成客户端没有CPU上升?

我可以在IIS级别执行此操作,但我需要从应用程序内部进行更多控制。

4 个答案:

答案 0 :(得分:37)

让我们来看看迈克尔的回答是否合理。

现在,迈克尔明智地指出Thread.Sleep(500)不应该在CPU的方式上花费太多。理论上这一切都很好,但是让我们看看这是否在实践中实现。

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

运行它,应用程序的CPU使用率徘徊在0%左右。

迈克尔还指出,由于ASP.NET必须使用的所有线程都处于休眠状态,因此必须生成新的线程,并提供这样做很昂贵。让我们试着不要睡觉,但要做很多产卵:

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

我们创建了很多线程,但它们只是执行一个空操作。这使用了大量的CPU,即使线程没有做任何事情。

但线程的总数永远不会变得非常高,因为每个线程的生命时间都很短。让两者结合起来:

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

将这个我们已经证明CPU使用率低的操作添加到每个线程会增加CPU的使用量,因为线程会增加。如果我在调试器中运行它,它会推高到接近100%的CPU。如果我在调试器之外运行它,它会更好一点,但只是因为它在它有机会达到100%之前抛出一个内存不足的异常。

因此,问题不在于Thread.Sleep本身,但是让所有可用线程睡眠的副作用迫使越来越多的线程被创建来处理其他工作,正如迈克尔所说。

答案 1 :(得分:31)

只是一个猜测:

我认为它不会占用CPU Thread.Sleep() - 这是因为你导致线程被绑定响应请求这么长时间,系统需要启动新线程(以及其他资源)响应新请求,因为线程池中不再提供这些休眠线程。

答案 2 :(得分:2)

您应该实现IHttpAsyncHandler,而不是ASP.NET页面。 ASP.NET页面代码在您的代码和浏览器之间放置了许多不适合传输二进制文件的东西。此外,由于您尝试执行速率限制,因此应使用异步代码来限制资源使用,这在ASP.NET页面中很难实现。 创建IHttpAsyncHandler非常简单。只需在BeginProcessRequest方法中触发一些异步操作,并且不要忘记正确关闭上下文以显示已到达文件末尾。 IIS将无法在此关闭它。

以下是我如何执行异步操作的一个非常糟糕的例子,该操作由一系列步骤组成,从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();
        }
    }
}

您还需要以适合您情况的方式向IIS注册IHttpAsyncHandler实施。

答案 3 :(得分:0)

因为线程每次产生时间片时都会获得优先级提升。避免经常打电话给睡眠(特别是低值)。