C# Parallel

2023-09-18 17:16:06 浏览数 (1)

1.概要

Parallel 具有多种静态方法,用于并行执行一组操作。这些方法可以显著提高处理大量数据时的性能,因为它们可以将工作负载分配到多个处理器核心或线程上。

1.1工作原理

Parallel 类的原理主要基于任务并行库(Task Parallel Library,TPL)。它依赖于多线程以及 CPU 的多核架构来并发地执行任务。以下是它的工作原理的详细说明:

1. 任务分解:

当你调用 Parallel 类的方法时(例如 Parallel.For 或者 Parallel.ForEach),TPL 首先会尝试将整个操作分解成一组更小的、可以独立运行的子任务。这种分解通常基于输入数据的数量和系统中可用的处理器核心数。

2. 任务调度:

然后,这些独立的任务会被放入一个全局队列中,等待被调度到不同的线程上执行。这个任务调度的过程由 .NET 运行时的线程池(ThreadPool)管理。线程池是一个维护着一组线程的池子,它的目标是复用这些线程来执行多个任务,减少线程创建和销毁的开销。

3. 任务执行:

线程池中的每个线程会从全局队列中取出一个任务来执行。如果一个线程完成了当前任务,它会再次从队列中取出新的任务来执行,直到所有的任务都被执行完毕。因为都是在单独的线程上执行任务,所以这些任务是并发执行的。

4. 异步与同步:

Parallel类的方法是同步方法,也就是说他们会阻塞当前线程直到所有并行任务都完成。如果需要异步执行并行任务,可以使用Task.Run或者Task.Factory.StartNew

请注意,并行编程具有一定的复杂性,特别是当任务需要访问共享资源或者彼此之间存在依赖时,我们需要使用其他机制(比如锁或者并发集合)来确保线程安全。

最后,虽然Parallel类可以改善计算密集型任务的性能,但对于IO密集型任务或者程序中有大量等待(比如网络调用)的情况,使用asyncawait来实现异步编程可能是更好的选择,因为它可以避免阻塞线程,提高应用程序的响应性。

1.2 Parallel的缺点

尽管 Parallel 类提供了并行执行代码的强大能力,但是它也有一些潜在的缺点和限制:

1. 复杂性增加:

并行编程比顺序编程更复杂。开发者需要小心处理数据竞争和同步问题,特别是当任务需要访问共享资源时。

2. 不一定总是提高性能:

并行处理并不总是带来性能上的提升。例如,对于 I/O 密集型操作或者单核 CPU,过度的线程分配可能会导致额外的开销,反而降低性能。

3. 任务调度开销:

分解任务并将它们调度到不同的线程上需要花费一定的时间。如果任务本身非常小,那么这种开销可能会超过并行处理带来的益处。

4. 无法控制执行顺序:

Parallel 类的方法并不能保证任务的执行顺序。如果任务之间有依赖关系,那么使用 Parallel 类可能会引入错误。

5. 难以调试:

并行程序的调试通常比顺序程序更困难,因为并行程序的执行路径可能有很多,而且每次运行的结果可能都不同。

6. 异常处理:

Parallel 的多个任务中收集和处理异常可能会更加复杂。例如,Parallel.ForParallel.ForEach 将在发生异常时立即停止所有处理,并抛出 AggregateException

鉴于以上的限制和挑战,最好只在确实需要改进性能或响应性时才使用并行处理,而且在使用时也要仔细考虑其潜在的影响。

1.3 合理使用Parallel

Parallel 类提供了强大的并行处理能力,但是我们需要理解其工作原理和潜在的挑战才能合理地使用它。以下是一些指导原则:

1. 选择合适的任务:

选择那些可以独立运行且无需访问共享资源的任务进行并行化。如果任务之间有依赖关系或者需要访问共享资源,可能需要额外的同步机制。

2. 考虑任务的大小:

如果任务本身非常小,那么将其分解为多个子任务并调度到不同的线程上可能会产生额外的开销,这可能会抵消并行处理带来的益处。确保每个任务的大小足够大,以便可以覆盖并行处理的开销。

3. 处理异常:

Parallel 中的任务中,你需要对可能发生的异常进行处理。否则,一个任务中的未捕获异常会导致所有任务停止执行并抛出 AggregateException

4. 测试和监视:

在引入并行处理后,要对应用程序进行充分的测试以确保它的正确性和性能。使用性能监视工具检查你是否实际上得到了预期的性能改进。

5. 运用适当的并行模式:

.NET 中有多种支持并行的技术和模式,如 Parallel 类、PLINQ、Task 和异步编程(async/await)等。根据应用程序的需求和特点选择最适合的模式。

请注意,虽然 Parallel 类可以简化并行编程,但是你仍然需要对多线程编程有深入的理解才能有效地使用它。

5. 限制并发任务数量:

当我们在使用Parallel时,它会自动根据CPU的核心数分配任务。有时候会导致单个应用在服务器上运行的时候对CPU的占用过高导致同台服务器的其他服务不能正常的运行,虽然我们并不能直接控制Parallel对核心数的占用但是可以间接的控制最大并发任务数量一定程度上减少但不完全控制CPU占用。

可以使用ParallelOptions对象来设置MaxDegreeOfParallelism属性,这个属性会限制并发执行的任务数:

代码语言:javascript复制
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };

Parallel.ForEach(sourceCollection, options, item =>
{
    // 处理逻辑
});

2.详细内容

以下是 Parallel 类中一些主要的方法:

1. Parallel.For

Parallel.For是一个静态方法,用于并行化for循环。例如:

代码语言:javascript复制
Parallel.For(0, 10, i =>
{
    Console.WriteLine(i);
});

这段代码会打印数字0到9。由于此循环是并行的,所以数字可能不按顺序打印。

2. Parallel.ForEach

Parallel.ForEach是另一个静态方法,用于并行化foreach循环。例如:

代码语言:javascript复制
var numbers = Enumerable.Range(0, 10);

Parallel.ForEach(numbers, number =>
{
    Console.WriteLine(number);
});

3. Parallel.Invoke

Parallel.Invoke 方法允许你并行执行一组方法。例如:

代码语言:javascript复制
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

其中,DoSomeWork()DoSomeOtherWork() 是两个独立的方法。

注意:并行处理通常适用于那些能够在没有相互依赖的情况下并行执行的任务。如果你的任务需要访问共享资源或者彼此间有依赖关系,然后你可能需要采用其它方式来控制并发,例如使用锁(lock)等机制。

3.Ref

https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel?view=net-7.0&devlangs=csharp&f1url=?appId=Dev16IDEF1&l=EN-US&k=k(System.Threading.Tasks.Parallel);k(DevLang-csharp)&rd=true

0 人点赞