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

我有一个带有这个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秒),当客户端数量增加时CPU使用率增加,当70到80个并发客户端CPU达到100%时CPU使用率增加并且任何新连接都被拒绝。 其他机器上的数字可能不同,但问题是为什么Thread.Sleep()是如此CPU密集型,有没有办法加速客户端没有CPU上升?

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

只是一个猜测:

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

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

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

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

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

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

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

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

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

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

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

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

您应该实现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实现。

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