为什么 Thread.Start 方法在 CPU 负载高时被阻塞?
Why Thread.Start method is blocked when CPU load is high?
出于测试目的,我编写了 CPU 压力程序:它只在 M 个线程中执行 N 个 for 循环。
我 运行 这个程序有很多线程,比如 200。
但是在任务管理器中我看到线程计数器不超过一些小值,比如 9 和一个 Thread.Start 方法 等待完成前一个 运行 宁线程 .
此行为似乎是线程池行为,但我希望常规 System.Threading.Thread
无论如何都必须启动,而无需等待某些原因。
下面的代码将重现此问题并提供解决方法选项:
using System;
using System.Diagnostics;
using System.Threading;
namespace HeavyLoad
{
class Program
{
static long s_loopsPerThread;
static ManualResetEvent s_startFlag;
static void Main(string[] args)
{
long totalLoops = (long)5e10;
int threadsCount = 200;
s_loopsPerThread = totalLoops / threadsCount;
Thread[] threads = new Thread[threadsCount];
var watch = Stopwatch.StartNew();
for (int i = 0; i < threadsCount; i++)
{
Thread t = new Thread(IntensiveWork);
t.IsBackground = true;
threads[i] = t;
}
watch.Stop();
Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds);
// *** Comment out s_startFlag creation to change the behavior ***
// s_startFlag = new ManualResetEvent(false);
watch = Stopwatch.StartNew();
foreach (var thread in threads)
{
thread.Start();
}
watch.Stop();
Console.WriteLine("Starting took {0} ms", watch.ElapsedMilliseconds);
if (s_startFlag != null)
s_startFlag.Set();
watch = Stopwatch.StartNew();
foreach (var thread in threads)
{
thread.Join();
}
watch.Stop();
Console.WriteLine("Waiting took {0} ms", watch.ElapsedMilliseconds);
Console.ReadLine();
}
private static void IntensiveWork()
{
if (s_startFlag != null)
s_startFlag.WaitOne();
for (long i = 0; i < s_loopsPerThread; i++)
{
// hot point
}
}
}
}
情况一:如果注释s_startFlag创建,则启动线程立即开始高强度CPU工作。在这种情况下,我有一个小的并发(大约 9 个线程)并且我一直坚持线程启动代码:
Creating took 0 ms
Starting took 4891 ms
Waiting took 63 ms
情况2:但是如果我创建s_startFlag,所有新线程都会等到它被设置。在这种情况下,我成功地同时启动了所有 200 个线程并获得了预期值:启动时间很短,工作时间很长,任务管理器中的线程数为 200+:
Creating took 0 ms
Starting took 27 ms
Waiting took 4733 ms
为什么线程在第一种情况下拒绝启动?我超过了什么样的限制?
系统:
- OS: Windows 7 专业
- 框架:NET 4.6
- CPU:Intel Core2 四核 Q9550 @ 2.83GHz
- 内存:8 Gb
我做了一些研究,现在我发现高 CPU 负载确实对线程启动时间有很大影响。
首先:为了有更多的观察时间,我将 totalLoops 设置为大 100 倍的值。我看到线程不受限制但创建速度非常慢。 1 线程在 1-2 秒内启动!
其次:我使用 SetThreadAffinityMask
函数 (https://sites.google.com/site/dotburger/threading/setthreadaffinitymask-1) 将主线程显式绑定到 CPU 核心 #0 并将工作线程绑定到核心 #1、#2、#3 .
Stopwatch watch;
using (ProcessorAffinity.BeginAffinity(0))
{
watch = Stopwatch.StartNew();
for (int i = 0; i < threadsCount; i++)
{
Thread t = new Thread(IntensiveWork);
t.IsBackground = true;
threads[i] = t;
}
watch.Stop();
Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds);
}
和
using (ProcessorAffinity.BeginAffinity(1, 2, 3))
{
for (long i = 0; i < s_loopsPerThread; i++)
{
}
}
现在主线程有自己专用的 CPU 核心(在进程边界)和工作线程在 ~10 毫秒后启动(totalLoops = 5e10)。
Creating took 0 ms
Starting took 2282 ms
Waiting took 3681 ms
另外,我在MSDN中找到了这句话:
When you call the Thread.Start method on a thread, that thread might
or might not start executing immediately, depending on the number of
processors and the number of threads currently waiting to execute.
https://msdn.microsoft.com/en-us/library/1c9txz50(v=vs.110).aspx
结论: Thread.Start 方法对活动线程数非常敏感。这可能会对性能产生非常大的影响 - 减慢数百倍。
出于测试目的,我编写了 CPU 压力程序:它只在 M 个线程中执行 N 个 for 循环。 我 运行 这个程序有很多线程,比如 200。 但是在任务管理器中我看到线程计数器不超过一些小值,比如 9 和一个 Thread.Start 方法 等待完成前一个 运行 宁线程 .
此行为似乎是线程池行为,但我希望常规 System.Threading.Thread
无论如何都必须启动,而无需等待某些原因。
下面的代码将重现此问题并提供解决方法选项:
using System;
using System.Diagnostics;
using System.Threading;
namespace HeavyLoad
{
class Program
{
static long s_loopsPerThread;
static ManualResetEvent s_startFlag;
static void Main(string[] args)
{
long totalLoops = (long)5e10;
int threadsCount = 200;
s_loopsPerThread = totalLoops / threadsCount;
Thread[] threads = new Thread[threadsCount];
var watch = Stopwatch.StartNew();
for (int i = 0; i < threadsCount; i++)
{
Thread t = new Thread(IntensiveWork);
t.IsBackground = true;
threads[i] = t;
}
watch.Stop();
Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds);
// *** Comment out s_startFlag creation to change the behavior ***
// s_startFlag = new ManualResetEvent(false);
watch = Stopwatch.StartNew();
foreach (var thread in threads)
{
thread.Start();
}
watch.Stop();
Console.WriteLine("Starting took {0} ms", watch.ElapsedMilliseconds);
if (s_startFlag != null)
s_startFlag.Set();
watch = Stopwatch.StartNew();
foreach (var thread in threads)
{
thread.Join();
}
watch.Stop();
Console.WriteLine("Waiting took {0} ms", watch.ElapsedMilliseconds);
Console.ReadLine();
}
private static void IntensiveWork()
{
if (s_startFlag != null)
s_startFlag.WaitOne();
for (long i = 0; i < s_loopsPerThread; i++)
{
// hot point
}
}
}
}
情况一:如果注释s_startFlag创建,则启动线程立即开始高强度CPU工作。在这种情况下,我有一个小的并发(大约 9 个线程)并且我一直坚持线程启动代码:
Creating took 0 ms
Starting took 4891 ms
Waiting took 63 ms
情况2:但是如果我创建s_startFlag,所有新线程都会等到它被设置。在这种情况下,我成功地同时启动了所有 200 个线程并获得了预期值:启动时间很短,工作时间很长,任务管理器中的线程数为 200+:
Creating took 0 ms
Starting took 27 ms
Waiting took 4733 ms
为什么线程在第一种情况下拒绝启动?我超过了什么样的限制?
系统:
- OS: Windows 7 专业
- 框架:NET 4.6
- CPU:Intel Core2 四核 Q9550 @ 2.83GHz
- 内存:8 Gb
我做了一些研究,现在我发现高 CPU 负载确实对线程启动时间有很大影响。
首先:为了有更多的观察时间,我将 totalLoops 设置为大 100 倍的值。我看到线程不受限制但创建速度非常慢。 1 线程在 1-2 秒内启动!
其次:我使用 SetThreadAffinityMask
函数 (https://sites.google.com/site/dotburger/threading/setthreadaffinitymask-1) 将主线程显式绑定到 CPU 核心 #0 并将工作线程绑定到核心 #1、#2、#3 .
Stopwatch watch;
using (ProcessorAffinity.BeginAffinity(0))
{
watch = Stopwatch.StartNew();
for (int i = 0; i < threadsCount; i++)
{
Thread t = new Thread(IntensiveWork);
t.IsBackground = true;
threads[i] = t;
}
watch.Stop();
Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds);
}
和
using (ProcessorAffinity.BeginAffinity(1, 2, 3))
{
for (long i = 0; i < s_loopsPerThread; i++)
{
}
}
现在主线程有自己专用的 CPU 核心(在进程边界)和工作线程在 ~10 毫秒后启动(totalLoops = 5e10)。
Creating took 0 ms
Starting took 2282 ms
Waiting took 3681 ms
另外,我在MSDN中找到了这句话:
When you call the Thread.Start method on a thread, that thread might or might not start executing immediately, depending on the number of processors and the number of threads currently waiting to execute.
https://msdn.microsoft.com/en-us/library/1c9txz50(v=vs.110).aspx
结论: Thread.Start 方法对活动线程数非常敏感。这可能会对性能产生非常大的影响 - 减慢数百倍。