C# 具有模划分的并行Foreach
使用C# 具有模划分的并行Foreach,c#,parallel.foreach,C#,Parallel.foreach,使用Parallel.Foreach()时,对于使用4个线程的100个项目,它会将列表分成4个项目块(0-24,25-49,50-74,75-99),这意味着项目0、25、50和75是并行处理的 是否有可能以模的方式对项目进行分区,以首先处理索引较低的项目?比如: Thread 1: 0, 5, 9,.. Thread 2: 1, 6, 10,... Thread 3: 2, 7, 11,... Thread 4: 3, 8, 12,... 这种分区方法称为循环(Round-Robin)或条带
Parallel.Foreach()
时,对于使用4个线程的100个项目,它会将列表分成4个项目块(0-24,25-49,50-74,75-99),这意味着项目0、25、50和75是并行处理的
是否有可能以模的方式对项目进行分区,以首先处理索引较低的项目?比如:
Thread 1: 0, 5, 9,..
Thread 2: 1, 6, 10,...
Thread 3: 2, 7, 11,...
Thread 4: 3, 8, 12,...
这种分区方法称为循环(Round-Robin)或条带化。将它与
Parallel.ForEach()
一起使用的主要挑战是ForEach()
需要分区器支持动态分区,这在这种类型的分区中是不可能的,因为分区数必须在执行循环之前固定
实现此类分区的一种方法是创建一个从System.Collections.Concurrent.Partitioner
派生的自定义类,并使用ParallelQuery.ForAll()
方法,该方法不具备动态分区支持要求。对于大多数应用程序,这应该相当于使用ForEach()
下面是一个自定义分区器的示例和一个基本实现。Partitioner
将生成与并行度相同数量的分区
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace RoundRobinPartitioning
{
public class RoundRobinPartitioner<TSource> : Partitioner<TSource>
{
private readonly IList<TSource> _source;
public RoundRobinPartitioner(IList<TSource> source)
{
_source = source;
}
public override bool SupportsDynamicPartitions { get { return false; } }
public override IList<IEnumerator<TSource>> GetPartitions(int partitionCount)
{
var enumerators = new List<IEnumerator<TSource>>(partitionCount);
for (int i = 0; i < partitionCount; i++)
{
enumerators.Add(GetEnumerator(i, partitionCount));
}
return enumerators;
}
private IEnumerator<TSource> GetEnumerator(
int partition,
int partitionCount)
{
int position = partition;
TSource value;
while (position < _source.Count)
{
value = _source[position];
position += partitionCount;
yield return value;
}
}
}
class Program
{
static void Main(string[] args)
{
var values = Enumerable.Range(0, 100).ToList();
var partitioner = new RoundRobinPartitioner<int>(values);
partitioner.AsParallel()
.WithDegreeOfParallelism(4)
.ForAll(value =>
{
// Perform work here
});
}
}
}
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Linq;
命名空间RoundRobinPartitioning
{
公共类RoundRobinPartitioner:Partitioner
{
私有只读IList_源;
公共RoundRobinPartitioner(IList来源)
{
_来源=来源;
}
公共覆盖布尔支持动态分区{get{return false;}}
公共覆盖IList GetPartitions(int partitionCount)
{
变量枚举数=新列表(分区计数);
对于(int i=0;i
{
//在这里工作
});
}
}
}
为什么需要这样做?请尝试指定分区器:并将列表分块,然后将每个分块发送到parallel foreach?不要这样做:每个CPU核心都有自己的缓存,缓存线为16字节;所以缓存开始相互感觉(锁定、检查锁定、重新加载等)。通常的规则是尽可能地将不同线程(任务)的数据放在一起。你怎么会认为它首先执行0、25、50和75?这正是我在问题中所问的。我忘了提的是:我想休息()。对于ForAll(),这可能吗?我可以设置一个中断标志,该标志作为要执行的操作中的第一个被检查,但我仍然必须遍历所有项。