C#拆分列表<;T>;使用TPL并行ForEach分组
我需要处理一个包含数千个元素的C#拆分列表<;T>;使用TPL并行ForEach分组,c#,split,parallel-processing,thread-safety,task-parallel-library,C#,Split,Parallel Processing,Thread Safety,Task Parallel Library,我需要处理一个包含数千个元素的列表 首先,我需要按年份和类型对元素进行分组,以便获得一个列表。然后,对于每个内部列表,我想添加类型为T的对象,直到列表达到最大包大小,然后我创建一个新包,并以相同的方式继续 我想使用并行.ForEach循环。 如果我按顺序运行,我的实际实现工作得很好,但是逻辑不是线程安全的,我想更改它。 我认为问题在于内部并行。ForEach循环,当列表达到最大大小时,我在同一引用中实例化了一个新的列表 private ConcurrentBag<ConcurrentBag
列表
首先,我需要按年份和类型对元素进行分组,以便获得一个列表
。然后,对于每个内部列表
,我想添加类型为T的对象,直到列表
达到最大包大小,然后我创建一个新包,并以相同的方式继续
我想使用并行.ForEach
循环。
如果我按顺序运行,我的实际实现工作得很好,但是逻辑不是线程安全的,我想更改它。
我认为问题在于内部并行。ForEach
循环,当列表
达到最大大小时,我在同一引用中实例化了一个新的列表
private ConcurrentBag<ConcurrentBag<DumpDocument>> InitializePackages()
{
// Group by Type and Year
ConcurrentBag<ConcurrentBag<DumpDocument>> groups = new ConcurrentBag<ConcurrentBag<DumpDocument>>(Dump.DumpDocuments.GroupBy(d => new { d.Type, d.Year })
.Select(g => new ConcurrentBag<DumpDocument> (g.ToList()))
.ToList());
// Documents lists with max package dimension
ConcurrentBag<ConcurrentBag<DumpDocument>> documentGroups = new ConcurrentBag<ConcurrentBag<DumpDocument>>();
foreach (ConcurrentBag<DumpDocument> group in groups)
{
long currentPackageSize = 0;
ConcurrentBag<DumpDocument> documentGroup = new ConcurrentBag<DumpDocument>();
ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = Parameters.MaxDegreeOfParallelism };
Parallel.ForEach(group, options, new Action<DumpDocument, ParallelLoopState>((DumpDocument document, ParallelLoopState state) =>
{
long currentDocumentSize = new FileInfo(document.FilePath).Length;
// If MaxPackageSize = 0 then no splitting to apply and the process works well
if (Parameters.MaxPackageSize > 0 && currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize)
{
documentGroups.Add(documentGroup);
// Here's the problem!
documentGroup = new ConcurrentBag<DumpDocument>();
currentPackageSize = 0;
}
documentGroup.Add(document);
currentPackageSize += currentDocumentSize;
}));
if (documentGroup.Count > 0)
documentGroups.Add(documentGroup);
}
return documentGroups;
}
public class DumpDocument
{
public string Id { get; set; }
public long Type { get; set; }
public string MimeType { get; set; }
public int Year { get; set; }
public string FilePath { get; set; }
}
我到处读到,我也可以使用分区器,但我从来没有使用过,而且目前这不是我的优先事项
我也读过类似的文章,但没有解决我的内循环问题
更新日期2016年12月28日
我更新了代码以满足验证要求。代码更新后,您似乎正在使用ConcurrentBag
,因此代码中还剩下另一个非线程安全逻辑:
long currentPackageSize = 0;
if (// .. &&
currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize
// ...
{
// ...
currentPackageSize += currentDocumentSize;
}
foreach (var group in groups)
+=
操作符不是原子的,在这里肯定会有竞争条件,读取long
变量的值在这里不是线程安全的。您可以在此处引入锁
,或使用以原子方式更新值:
Interlocked.Add(ref currentPackageSize, currentDocumentSize);
Interlocked.Exchange(ref currentPackageSize, 0);
Interlocked.Read(ref currentPackageSize);
使用这个类将导致一些重构代码(我认为在您的情况下,使用CAS
操作,如compareeexchange
更可取),因此,对于您来说,使用锁可能是最简单的方法。您可能应该实现这两种方法并测试它们,并测量执行时间)
此外,正如您所看到的,实例化也不是线程安全的,因此您必须锁定变量(这将导致线程同步暂停)或将代码重构为两个步骤:首先并行获取所有文件大小,然后按顺序迭代结果,避免争用条件
至于分区器
,您不应该在这里使用此类,因为它通常用于跨CPU调度工作,而不是分割结果
但是,我想指出,您有一些小的代码问题:
您可以删除ConcurrentBag
的构造函数中的ToList()
调用,因为它接受IEnumerable
,您已经拥有:
ConcurrentBag<ConcurrentBag<DumpDocument>> groups = new ConcurrentBag<ConcurrentBag<DumpDocument>>(Dump.DumpDocuments.GroupBy(d => new { d.Type, d.Year })
.Select(g => new ConcurrentBag<DumpDocument> (g)));
除非您知道自己在做什么(我认为您不知道),否则不应该使用最大程度的并行性:
TPL
默认任务计划程序会尝试调整任务的线程池和CPU使用率,因此通常情况下,此数字应等于Environment.ProcessorCount
您可以对Parallel.ForEach
使用lambda
语法,并且不创建新的操作
(您也可以将此代码移到方法例程):
(文档、状态)=>
{
long currentDocumentSize=新文件信息(document.FilePath).Length;
//如果MaxPackageSize=0,则无需应用拆分,该过程运行良好
如果(Parameters.MaxPackageSize>0&¤tPackageSize+currentDocumentSize>Parameters.MaxPackageSize)
{
documentGroups.Add(documentGroup);
//问题来了!
documentGroup=新的ConcurrentBag();
currentPackageSize=0;
}
documentGroup.Add(文档);
currentPackageSize+=currentDocumentSize;
}
lambda被正确编译,因为您已经有了一个通用集合(一个包),并且有一个重载接受ParallelLoopState
作为第二个参数
你只是想通过并行来加速事情吗?在这个特定的例子中,是的。我想加快包的初始化速度。然后每个包(ConcurrentBag)通过一个更复杂的并行ForEach循环来处理文档。在一个线程上处理数据几乎总是比并行处理更快。只有当您有一些繁重的处理时,才值得并行地做任何事情。但是如果我需要在循环中做一些更复杂的事情呢?如何使代码线程安全?这是我的问题…当然。你能修改你的代码,使之成为一个好代码吗?那就可以回答了,谢谢!我最终通过两个步骤重构了代码。但我也很感激你在这门互锁的课上的夸夸其谈。在我的代码中,我在其他一些ParallelForEach块上使用了它,特别是在计数器上。不客气:)是的,对于计数器,这样的增量非常有用。祝你的项目好运。
foreach (var group in groups)
var options = new ParallelOptions { MaxDegreeOfParallelism = Parameters.MaxDegreeOfParallelism };
(document, state) =>
{
long currentDocumentSize = new FileInfo(document.FilePath).Length;
// If MaxPackageSize = 0 then no splitting to apply and the process works well
if (Parameters.MaxPackageSize > 0 && currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize)
{
documentGroups.Add(documentGroup);
// Here's the problem!
documentGroup = new ConcurrentBag<DumpDocument>();
currentPackageSize = 0;
}
documentGroup.Add(document);
currentPackageSize += currentDocumentSize;
}