使用ConcurrentBag与一个简单数组在并行for

本文关键字:数组 简单 并行 for 一个 ConcurrentBag 使用 | 更新日期: 2023-09-27 18:11:09

这是一个假设的问题/用例,基于在这个特定用例中使用(在这种情况下)ConcurrentBag而不是Parallel for循环中的一个简单数组的好处。

该场景基于使用一个通用的Pipeline模式来分析从1到结果的数字,并基于某个Pipeline操作的输出不为空来存储结果

结果列表的实际顺序很重要,因此使用简单列表(字符串类型)。Add将导致基于每个线程决定何时返回结果的奇怪。

我有以下工作代码:

    public IList<string> Execute(int total)
    {
        var items = new ConcurrentBag<AnalyzerResult>();
        Parallel.ForEach(Iterate(1, (total + 1)), d =>
        {
            foreach (IOperation<T> operation in operations)
            {
                var result = operation.Execute(d);
                if (result != null)
                {
                    items.Add(new AnalyzerResult(d, result));
                    break;
                }
            }
        });
        return items.OrderBy(o=>o.SortOrder).Select(d => d.Result).ToList();
    }

AnalyzerResult是一个简单的不可变类,代码只会将新项推送到包中(因此理论上没有items列表中的内容被更改的危险)。

基于此,是否一个简单的数组是足够的(并包含较少的代码噪音)?或者使用并发类型会被认为是更好的实践/性能更高吗?例如:

    public IList<string> Execute(int total)
    {
        var items = new string[total];
        Parallel.ForEach(Iterate(1, (total + 1)), d =>
        {
            foreach (IOperation<T> operation in operations)
            {
                var result = operation.Execute(d);
                if (result != null)
                {
                    items[(d - 1)] = result;
                    break;
                }
            }
        });
        return items.ToList();
    }

注意:这不是并发性问题,两个方法都是合法的,并且产生期望的结果没有问题。

使用ConcurrentBag与一个简单数组在并行for

我最初的回答是"你需要并发保护",但后来重读了你问题的第二部分。

这看起来应该可以工作,因为您不会尝试从两个不同的线程写入内存中的相同位置。因此,消除锁和线程亲和性(ConcurrentBag提供的)应该可以显著提高性能。

真正的问题是-增加了多少,这是一个必要的增加(需要配置文件),你是否会在将来改变这个设置,这样你就需要并发保护。

就这样,它应该很好,而且可读性很好。您可能想要注释这段代码,以说明为什么这样做,以确保有人不会随意浏览它并认为"并发问题"(就像我刚才做的那样)并"修复"它。