C# 使用任务并行库时线程数的增长
我使用的是C#TPL,我的生产者/消费者代码有问题。。。出于某种原因,TPL不重用线程,而是不断地创建新线程而不停止 我举了一个简单的例子来说明这种行为:C# 使用任务并行库时线程数的增长,c#,multithreading,parallel-processing,task-parallel-library,producer-consumer,C#,Multithreading,Parallel Processing,Task Parallel Library,Producer Consumer,我使用的是C#TPL,我的生产者/消费者代码有问题。。。出于某种原因,TPL不重用线程,而是不断地创建新线程而不停止 我举了一个简单的例子来说明这种行为: class Program { static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1); static CancellationTokenSource m_Cts = new CancellationTokenSo
class Program
{
static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1);
static CancellationTokenSource m_Cts = new CancellationTokenSource();
static void Producer()
{
try
{
while (!m_Cts.IsCancellationRequested)
{
Console.WriteLine("Enqueuing job");
m_Buffer.Add(0);
Thread.Sleep(1000);
}
}
finally
{
m_Buffer.CompleteAdding();
}
}
static void Consumer()
{
Parallel.ForEach(m_Buffer.GetConsumingEnumerable(), Run);
}
static void Run(int i)
{
Console.WriteLine
("Job Processed\tThread: {0}\tProcess Thread Count: {1}",
Thread.CurrentThread.ManagedThreadId,
Process.GetCurrentProcess().Threads.Count);
}
static void Main(string[] args)
{
Task producer = new Task(Producer);
Task consumer = new Task(Consumer);
producer.Start();
consumer.Start();
Console.ReadKey();
m_Cts.Cancel();
Task.WaitAll(producer, consumer);
}
}
类程序
{
静态BlockingCollection m_Buffer=新BlockingCollection(1);
静态CancellationTokenSource m_Cts=新的CancellationTokenSource();
静态空隙发生器()
{
尝试
{
而(!m_Cts.iscancellationrequest)
{
控制台写入线(“排队作业”);
m_Buffer.Add(0);
睡眠(1000);
}
}
最后
{
m_Buffer.CompleteAdding();
}
}
静态无效使用者()
{
Parallel.ForEach(m_Buffer.getconsumineGenumerable(),Run);
}
静态无效运行(int i)
{
控制台写入线
(“已处理作业\t线程:{0}\t进程线程计数:{1}),
Thread.CurrentThread.ManagedThreadId,
Process.GetCurrentProcess().Threads.Count);
}
静态void Main(字符串[]参数)
{
任务生产者=新任务(生产者);
任务使用者=新任务(使用者);
producer.Start();
consumer.Start();
Console.ReadKey();
m_Cts.Cancel();
Task.WaitAll(生产者、消费者);
}
}
此代码创建两个任务,生产者和消费者。Products每秒添加1个工作项,而Consumer只打印出一个包含信息的字符串。我假设在这种情况下,1个使用者线程就足够了,因为任务的处理速度比添加到队列的速度快得多,但实际发生的情况是,进程中的线程数每秒增加1。。。就好像TPL正在为每个项目创建新线程一样
在试图了解发生了什么之后,我还注意到了另一件事:即使BlockingCollection大小为1,但在一段时间后,消费者开始被突然调用,例如,它是这样开始的:
排队作业
作业处理线程:4进程线程计数:9
排队作业
作业处理线程:6进程线程计数:9
排队作业
作业处理线程:5进程线程计数:10
排队作业
作业处理线程:4进程线程计数:10
排队作业
作业处理线程:6进程线程计数:11
这就是它在不到一分钟后处理项目的方式:
排队作业
作业已处理线程:25进程线程计数:52
排队作业
排队作业
作业处理线程:5进程线程计数:54
作业处理线程:5进程线程计数:54
由于线程在完成Parallel.ForEach循环后被释放(我在本例中没有显示,但它是在实际项目中),所以我假设它与ForEach有关。。。我发现了这篇文章,我认为我的问题是由这个默认分区器引起的,所以我从TPL示例中获取了自定义分区器,它一个接一个地为消费者线程项提供信息,尽管它修复了执行顺序(消除了延迟)
排队作业
作业处理线程:71进程线程计数:140
排队作业
作业处理线程:12进程线程计数:141
排队作业
作业处理线程:72进程线程计数:142
排队作业
作业处理线程:38进程线程计数:143
排队作业
作业处理线程:73进程线程计数:143
排队作业
作业处理线程:21进程线程计数:144
排队作业
作业已处理线程:74进程线程计数:145
…它并没有阻止线程的增长
我知道ParallelOptions.MaxDegreeOfParallelism,但我仍然想了解TPL发生了什么,以及为什么它会毫无理由地创建数百个线程
在我的项目中,我需要运行数小时,从数据库中读取新数据,将其放入BlockingCollections,并由其他代码处理数据,大约每5秒有一个新项,处理它需要几毫秒到几乎一分钟,运行大约10分钟后,线程数超过1000个线程有两个因素共同导致此行为:
class Program
{
static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1);
static CancellationTokenSource m_Cts = new CancellationTokenSource();
static void Producer()
{
try
{
while (!m_Cts.IsCancellationRequested)
{
Console.WriteLine("Enqueuing job");
m_Buffer.Add(0);
Thread.Sleep(1000);
}
}
finally
{
m_Buffer.CompleteAdding();
}
}
static void Consumer()
{
Parallel.ForEach(m_Buffer.GetConsumingEnumerable(), Run);
}
static void Run(int i)
{
Console.WriteLine
("Job Processed\tThread: {0}\tProcess Thread Count: {1}",
Thread.CurrentThread.ManagedThreadId,
Process.GetCurrentProcess().Threads.Count);
}
static void Main(string[] args)
{
Task producer = new Task(Producer);
Task consumer = new Task(Consumer);
producer.Start();
consumer.Start();
Console.ReadKey();
m_Cts.Cancel();
Task.WaitAll(producer, consumer);
}
}
ThreadPool
尝试根据您的情况使用最佳线程数。但是,如果池中的一个线程阻塞,池就会认为该线程没有做任何有用的工作,因此它倾向于在这之后不久创建另一个线程。这意味着,如果有大量的阻塞,ThreadPool
在猜测最佳线程数方面非常糟糕,它倾向于创建新线程,直到达到极限Parallel.ForEach()Parallel.ForEach()
主要用于有界集合,而不是数据流
getConsumineumerable()
结合起来时,您得到的是Parallel.ForEach()
创建的线程几乎总是被阻塞的。ThreadPool
看到了这一点,为了保持CPU的利用率,创建了越来越多的线程
正确的解决方案是设置MaxDegreeOfParallelism
。如果您的计算受CPU限制,则最有可能是最佳值。如果它们受IO约束,则必须通过实验找出最佳值
如果可以使用.NET4.5,另一个选项是使用TPL数据流。这个库是专门用来处理数据流的,就像你所拥有的,所以它没有问题