Parallel.ForEach不会产生所有线程

时间:2021-11-11 20:59:44

I'm having some trouble using Parallel.ForEach. I need to simulate a couple of hardware components, that wait for an incoming connection, and reply to it.

我在使用Parallel.ForEach时遇到了一些麻烦。我需要模拟一些等待传入连接的硬件组件,然后回复它。

My current code is as follows:

我目前的代码如下:

Task.Factory.StartNew(() => components, (component) =>
    {
        var listener = new TcpListener(component.Ip, component.Port);
        while(true)
        {
            using(var socket = listener.AcceptSocket())
            {
                 //Read out socket and send a reply
                 socket.Close();
            }
        }
    });

The problem I'm having is: Not every component will get his own thread created. Even if one of the threads exits, they still won't spawn.

我遇到的问题是:并非每个组件都会创建自己的线程。即使其中一个线程退出,它们仍然不会产生。

The current amount of components in my collection is 40, the number of threads spawned is (or at least seems to be) 33.

我的集合中当前的组件数量是40,产生的线程数是(或至少似乎是)33。

I was under the impression that Parallel.Foreach() would create a new, parallel, thread for the enumerable collection passed to it.

我的印象是Parallel.Foreach()会为传递给它的可枚举集合创建一个新的并行线程。

Any ideas what I'm doing wrong?

我有什么想法我做错了吗?

1 个解决方案

#1


3  

It doesn't necessarily start all the threads for each task at once. It looks at its workload and provisions that across all the cores of the processor(s). If you have more tasks than cores it will stop creating new threads as that would just lead to lots of unnecessary context switching. However, if it thinks existing tasks/threads are blocked, in which case it adds more threads so that work can continue, i.e. starting more tasks, while other tasks are blocked. It won't detect blocked tasks for a short period.

它不一定一次启动每个任务的所有线程。它着眼于处理器所有核心的工作负载和规定。如果你有比核心更多的任务,它将停止创建新线程,因为这会导致许多不必要的上下文切换。但是,如果它认为现有任务/线程被阻止,在这种情况下它会添加更多线程,以便可以继续工作,即启动更多任务,同时阻止其他任务。它不会在短时间内检测到被阻止的任务。

This probably explains why you are not seeing as many threads as tasks. As tasks finish, the system can re-use the thread it was on to put a new, as yet unstarted, task on it.

这可能解释了为什么你没有看到与任务一样多的线程。当任务完成后,系统可以重新使用它所使用的线程,在其上放置一个新的,尚未启动的任务。

The graph at the bottom of this blog post roughly illustrates this to some extent: http://colinmackay.co.uk/2011/02/08/parallelisation-in-net-40-part-1-looping/. Running up to 4 tasks took about the same length of time as just one. Then there is a jump when the 5th task is added and the time taken to complete was roughly the same up-to the 8th task, when it jumped again. This is because I was on a 4 core system.

这篇博客文章底部的图表大致说明了这一点:http://colinmackay.co.uk/2011/02/08/parallelisation-in-net-40-part-1-looping/。运行最多4个任务所花费的时间与仅一个任务相同。然后在添加第5个任务时跳转,并且完成所需的时间与第8个任务大致相同,当它再次跳跃时。这是因为我使用的是4核心系统。

UPDATE

UPDATE

Just realised that your code will never exit a task as you have an infinite loop in there. I would say that tasks (which are discrete units of work) are not what you want. Unless there is something else you are specifically getting from the Task Parallel Library then using regular threads yourself may be a better solution in this case.

刚刚意识到你的代码永远不会退出任务,因为你在那里有一个无限循环。我会说任务(这是离散的工作单元)不是你想要的。除非您从Task Parallel Library中获得其他内容,否则在这种情况下使用常规线程可能是更好的解决方案。

With tasks you have little control over when the threads get created or how many at a time. (You can write your own scheduler to control this if you are getting other things from the TPL you want to preserve). However, if you are simply starting up a background thread that constantly listens for stuff throughout the lifetime of your application then I'd still go with regular threads.

使用任务,您几乎无法控制创建线程的时间或一次创建多少线程。 (如果要从要保留的TPL中获取其他内容,您可以编写自己的调度程序来控制它。但是,如果您只是在应用程序的整个生命周期中启动一个不断监听内容的后台线程,那么我仍然会使用常规线程。

#1


3  

It doesn't necessarily start all the threads for each task at once. It looks at its workload and provisions that across all the cores of the processor(s). If you have more tasks than cores it will stop creating new threads as that would just lead to lots of unnecessary context switching. However, if it thinks existing tasks/threads are blocked, in which case it adds more threads so that work can continue, i.e. starting more tasks, while other tasks are blocked. It won't detect blocked tasks for a short period.

它不一定一次启动每个任务的所有线程。它着眼于处理器所有核心的工作负载和规定。如果你有比核心更多的任务,它将停止创建新线程,因为这会导致许多不必要的上下文切换。但是,如果它认为现有任务/线程被阻止,在这种情况下它会添加更多线程,以便可以继续工作,即启动更多任务,同时阻止其他任务。它不会在短时间内检测到被阻止的任务。

This probably explains why you are not seeing as many threads as tasks. As tasks finish, the system can re-use the thread it was on to put a new, as yet unstarted, task on it.

这可能解释了为什么你没有看到与任务一样多的线程。当任务完成后,系统可以重新使用它所使用的线程,在其上放置一个新的,尚未启动的任务。

The graph at the bottom of this blog post roughly illustrates this to some extent: http://colinmackay.co.uk/2011/02/08/parallelisation-in-net-40-part-1-looping/. Running up to 4 tasks took about the same length of time as just one. Then there is a jump when the 5th task is added and the time taken to complete was roughly the same up-to the 8th task, when it jumped again. This is because I was on a 4 core system.

这篇博客文章底部的图表大致说明了这一点:http://colinmackay.co.uk/2011/02/08/parallelisation-in-net-40-part-1-looping/。运行最多4个任务所花费的时间与仅一个任务相同。然后在添加第5个任务时跳转,并且完成所需的时间与第8个任务大致相同,当它再次跳跃时。这是因为我使用的是4核心系统。

UPDATE

UPDATE

Just realised that your code will never exit a task as you have an infinite loop in there. I would say that tasks (which are discrete units of work) are not what you want. Unless there is something else you are specifically getting from the Task Parallel Library then using regular threads yourself may be a better solution in this case.

刚刚意识到你的代码永远不会退出任务,因为你在那里有一个无限循环。我会说任务(这是离散的工作单元)不是你想要的。除非您从Task Parallel Library中获得其他内容,否则在这种情况下使用常规线程可能是更好的解决方案。

With tasks you have little control over when the threads get created or how many at a time. (You can write your own scheduler to control this if you are getting other things from the TPL you want to preserve). However, if you are simply starting up a background thread that constantly listens for stuff throughout the lifetime of your application then I'd still go with regular threads.

使用任务,您几乎无法控制创建线程的时间或一次创建多少线程。 (如果要从要保留的TPL中获取其他内容,您可以编写自己的调度程序来控制它。但是,如果您只是在应用程序的整个生命周期中启动一个不断监听内容的后台线程,那么我仍然会使用常规线程。