【发布时间】:2011-06-25 14:13:39
【问题描述】:
我正在做一个产生数百个线程的项目。所有这些线程都处于“休眠”状态(它们被锁定在 Monitor 对象上)。我注意到,如果我增加“睡眠”线程的数量,程序会非常慢。 “有趣”的是,查看任务管理器似乎线程数越多,处理器就越空闲。我已将问题缩小到对象创建。
谁能给我解释一下?
我已经制作了一个小样本来测试它。这是一个控制台程序。它为每个处理器创建一个线程,并通过一个简单的测试(“new Object()”)测量它的速度。不,“new Object()”并没有被忽略(如果你不信任我,请尝试)。主线程显示每个线程的速度。按下 CTRL-C,程序生成 50 个“休眠”线程。减速开始时只有 50 个线程。大约 250 时,在任务管理器上很明显 CPU 没有 100% 使用(我的使用率为 82%)。
我尝试了三种锁定“睡眠”线程的方法:Thread.CurrentThread.Suspend()(不好,不好,我知道 :-)),锁定已经锁定的对象和 Thread.Sleep(Timeout.无限的)。一样的。如果我用 new Object() 注释该行,并用 Math.Sqrt 替换它(或什么都没有),则问题不存在。速度不随线程数而变化。 其他人可以检查吗?有谁知道瓶颈在哪里?
啊...您应该在发布模式下对其进行测试,而无需从 Visual Studio 中启动它。 我在双处理器(无 HT)上使用 XP sp3。我已经使用 .NET 3.5 和 4.0 对其进行了测试(以测试不同的框架运行时)
namespace TestSpeed
{
using System;
using System.Collections.Generic;
using System.Threading;
class Program
{
private const long ticksInSec = 10000000;
private const long ticksInMs = ticksInSec / 1000;
private const int threadsTime = 50;
private const int stackSizeBytes = 256 * 1024;
private const int waitTimeMs = 1000;
private static List<int> collects = new List<int>();
private static int[] objsCreated;
static void Main(string[] args)
{
objsCreated = new int[Environment.ProcessorCount];
Monitor.Enter(objsCreated);
for (int i = 0; i < objsCreated.Length; i++)
{
new Thread(Worker).Start(i);
}
int[] oldCount = new int[objsCreated.Length];
DateTime last = DateTime.UtcNow;
Console.Clear();
int numThreads = 0;
Console.WriteLine("Press Ctrl-C to generate {0} sleeping threads, Ctrl-Break to end.", threadsTime);
Console.CancelKeyPress += (sender, e) =>
{
if (e.SpecialKey != ConsoleSpecialKey.ControlC)
{
return;
}
for (int i = 0; i < threadsTime; i++)
{
new Thread(() =>
{
/* The same for all the three "ways" to lock forever a thread */
//Thread.CurrentThread.Suspend();
//Thread.Sleep(Timeout.Infinite);
lock (objsCreated) { }
}, stackSizeBytes).Start();
Interlocked.Increment(ref numThreads);
}
e.Cancel = true;
};
while (true)
{
Thread.Sleep(waitTimeMs);
Console.SetCursorPosition(0, 1);
DateTime now = DateTime.UtcNow;
long ticks = (now - last).Ticks;
Console.WriteLine("Slept for {0}ms", ticks / ticksInMs);
Thread.MemoryBarrier();
for (int i = 0; i < objsCreated.Length; i++)
{
int count = objsCreated[i];
Console.WriteLine("{0} [{1} Threads]: {2}/sec ", i, numThreads, ((long)(count - oldCount[i])) * ticksInSec / ticks);
oldCount[i] = count;
}
Console.WriteLine();
CheckCollects();
last = now;
}
}
private static void Worker(object obj)
{
int ix = (int)obj;
while (true)
{
/* First and second are slowed by threads, third, fourth, fifth and "nothing" aren't*/
new Object();
//if (new Object().Equals(null)) return;
//Math.Sqrt(objsCreated[ix]);
//if (Math.Sqrt(objsCreated[ix]) < 0) return;
//Interlocked.Add(ref objsCreated[ix], 0);
Interlocked.Increment(ref objsCreated[ix]);
}
}
private static void CheckCollects()
{
int newMax = GC.MaxGeneration;
while (newMax > collects.Count)
{
collects.Add(0);
}
for (int i = 0; i < collects.Count; i++)
{
int newCol = GC.CollectionCount(i);
if (newCol != collects[i])
{
collects[i] = newCol;
Console.WriteLine("Collect gen {0}: {1}", i, newCol);
}
}
}
}
}
【问题讨论】:
-
如果您关心性能,您的线程数不应超过 (cpucount) 个。 (cpucount+2) 和 (cpucount*2) 之间是很好的经验法则(在您的系统上,两者都是 4)。使用异步 I/O 操作队列使少数线程保持忙碌而不是休眠。线程应该等待的唯一时间是在争夺锁时。
-
我正在做一个“慢动作”协程。线程之间的“切换时间”是无关紧要的,所以我可以使用线程(我有大约一个“切换”/秒,所以即使它失去了一些毫秒来在旧线程和新线程之间切换我没有任何问题)。总是有许多线程运行等于处理器但是如果睡眠线程减慢一切,那么我就有问题了。不,我不能使用 MS 的异步库,因为它是“假的”。它“重写”你的程序。我必须使用一些预先存在的库。
-
您是否考虑过使用 TPL 而不是显式创建线程?这样,框架可以决定最合适的本地线程数来完成这项工作。
-
@Richard 我的问题是我想要协程,而 .NET 没有实现它们。我正在使用多个线程模拟它们。我什至尝试过使用异步 CTP,但预刷新速度很慢。我必须在刷新后重试。
标签: c# .net multithreading performance asynchronous