【C#】Parallel.ForEach导致内存不足问题解决方案

本文介绍了如何在C#中使用Parallel.ForEach()方法提高数据处理效率,并探讨了如何避免因不当使用而导致的“OutofMemory”错误。文章还提供了一些实践经验和参数调整建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在对大量数据进行处理或计算的时候,使用并行提高效率是经常使用的做法,在C#中提供了Parallel.ForEach()方法代替foreach来进行并行处理,提高效率,它使用方法很简单,伪代码如下:

using System.Threading.Tasks;

Parallel.ForEach(Datas,(data)=>
{
    //对data进行计算或处理
}
);

其中第一个参数Datas是我们的数据集合,第二个参数为一个action委托,用来对集合中的数据进行处理或者计算,如果使用适当,能够有效提高计算效率,但是在使用不当的情况下就可能会出现“Out of Memory”的错误,通过在网上搜索,最终在StackOverflow上找到了对这个问题解释的比较好的一个回答,现分享在这里。

问题分析及解决方案

Parallel.ForEach的默认选项只有当任务时CPU密集型且CPU利用率线性增长时才有很好的表现,当任务是CPU密集型时,一切都很完美。如果你有一个四核的CPU且没有其他进程在运行,那么Parallel.ForEach可以使用所有四个处理器。如果你有一个四核CPU并且同时有一个进程在使用一个完整的处理器,那么Parallel.ForEach大约会使用三个处理器。

但是如果任务不是CPU密集型,那么Parallel.ForEach将会一直开启新任务,努力维持CPU的满载运行,但是无论多少任务在并行执行,总会有未使用的CPU资源所以它就会不断创建新任务。

那么怎么判断你的任务是否是CPU密集型的呢?最好是通过观察就能够知道,比如你正在进行分解质因数的操作,那很显然这就是一个CPU密集型任务。但是大部分情况下都很难一眼看出是否是CPU密集型操作,所以一般情况下的经验方法是通过限制最大并行数量(ParallelOptions.MaximumDegreeOfParallelism)并观察程序的表现,如果你的任务是CPU密集型你将会看到(四核情况):

  • ParallelOptions.MaximumDegreeOfParallelism = 1 :CPU使用率25%或使用一个CPU
  • ParallelOptions.MaximumDegreeOfParallelism = 2 :CPU使用率50%或使用两个CPU
  • ParallelOptions.MaximumDegreeOfParallelism = 4 :CPU使用率100%或使用所有CPU

当你观察到如上的情况,那么你就可以使用默认的Parallel.ForEach选项并且得到很好的结果。线性的CPU利用率意味着良好的任务调度。

但是当我在Intel I7处理器上运行你的示例代码时,无论我设置多大的最大并行数,我只能得到20%的CPU利用率,为什么会这样呢?因为分配的内存太多导致GC阻塞了线程。显然,这个程序是资源密集型的,而这里的资源就是---内存!

同样,对数据库服务器执行长时间运行查询的IO密集型任务也永远无法有效利用本地计算机上的所有可用CPU资源。在这种情况下,任务调度程序无法“知道何时停止”启动新任务。

如果你的任务不是CPU密集型或者CPU的利用率在最大并行数量下不是线性增长的,那么建议你在使用Parallel.ForEach的时候不要一次性开启太多任务。最简单的方法是为并行数MaxDegreeOfParallelism指定一个数值(设置ForEach的第二个参数ParallelOptions),允许对重复的I/O密集型任务进行一定程度的并行处理,但不要太多,以免本地计算机对资源的需求过大,或使远程服务器负担过重。为了获得最佳设置参数,需要不断反复试验。

static void Main(string[] args)
{
    Parallel.ForEach(CreateData(),
        new ParallelOptions { MaxDegreeOfParallelism = 4 },
        (data) =>
            {
                data[0] = 1;
            });
}

### C# 中 `Parallel` 类的使用方法与说明 `Parallel` 类是 .NET 框架中用于并行编程的重要工具,它允许开发者通过简单的 API 实现多线程任务的并行化。以下是关于 `Parallel` 类的详细说明和示例。 #### 1. 并行循环:`Parallel.For` 和 `Parallel.ForEach` `Parallel.For` 和 `Parallel.ForEach` 是两个常用的静态方法,用于将传统的循环结构并行化。 - **`Parallel.For` 示例**: ```csharp using System; using System.Threading.Tasks; class Program { static void Main() { Parallel.For(0, 10, i => { Console.WriteLine($"Processing {i} on thread {Task.CurrentId}"); }); } } ``` 上述代展示了如何使用 `Parallel.For` 方法并行处理从 0 到 9 的整数集合[^1]。 - **`Parallel.ForEach` 示例**: ```csharp using System; using System.Collections.Generic; using System.Threading.Tasks; class Program { static void Main() { List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; Parallel.ForEach(numbers, number => { Console.WriteLine($"Processing {number} on thread {Task.CurrentId}"); }); } } ``` 此代片段展示了如何使用 `Parallel.ForEach` 方法对列表中的每个元素执行并行操作[^2]。 #### 2. 线程安全实践 在并行编程中,线程安全是一个重要问题。以下是一个确保线程安全的 `Parallel.ForEach` 示例: ```csharp using System; using System.Collections.Generic; using System.Threading.Tasks; class Program { static void Main() { List<int> numbers = new List<int>(); for (int i = 0; i < 1000; i++) numbers.Add(i); int sum = 0; object lockObject = new object(); Parallel.ForEach(numbers, () => 0, (number, loopState, localSum) => { localSum += number; return localSum; }, localSum => { lock (lockObject) { sum += localSum; } }); Console.WriteLine("Sum: " + sum); } } ``` 在此示例中,通过使用局部变量 `localSum` 和 `lock` 关键字来确保累加操作的线程安全性[^2]。 #### 3. 并行调用:`Parallel.Invoke` `Parallel.Invoke` 方法用于同时启动多个独立的任务。 ```csharp using System; using System.Threading.Tasks; class Program { static void Main() { Console.WriteLine("Parallel Start"); Parallel.Invoke( () => Test1(), () => Test2() ); Console.WriteLine("Parallel End"); } static void Test1() { for (int a = 0; a < 20; a++) { Console.WriteLine($"Task1: {a}, Thread ID: {System.Threading.Thread.CurrentThread.ManagedThreadId}"); } } static void Test2() { for (int b = 0; b < 20; b++) { Console.WriteLine($"Task2: {b}, Thread ID: {System.Threading.Thread.CurrentThread.ManagedThreadId}"); } } } ``` 此代展示了如何使用 `Parallel.Invoke` 同时运行两个独立的任务[^3]。 #### 4. 常见问题与解决方法 - **非线程安全集合的使用**:如果在并行任务中使用非线程安全的集合(如 `List<T>`),可能会导致数据不一致。解决方案是使用线程安全的集合,例如 `ConcurrentBag<T>`[^4]。 - **性能优化**:在设计并行任务时,应避免过多的上下文切换和锁操作,以提高性能[^4]。 ### 注意事项 - 在使用 `Parallel` 类时,确保任务之间不存在竞争条件或死锁问题。 - 避免在并行任务中直接修改共享资源,除非采取适当的同步措施。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值