如何在C#中使用Parallel.For和Parallel.ForEach
作者:admin 时间:2023-6-5 17:29:11 浏览:并行性是在具有多个内核的系统上并行执行任务的能力。.NET Framework 4 中引入了对 .NET 中并行编程的支持。.NET 中的并行编程使我们能够更有效地使用系统资源,并通过更好的编程控制。本文讨论了我们如何在 .NET Core 应用程序中使用并行性。
要使用本文中提供的代码示例,你应该在系统中安装 Visual Studio 2019。
在 Visual Studio 中创建 .NET Core 控制台应用程序项目
首先,让我们在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目。假设你的系统中安装了 Visual Studio 2019,请按照下面概述的步骤在 Visual Studio 中创建一个新的 .NET Core 控制台应用程序项目。
- 启动 Visual Studio IDE。
- 单击“创建新项目”。
- 在“创建新项目”窗口中,从显示的模板列表中选择“控制台应用程序(.NET Core)”。
- 点击下一步。
- 在“配置新项目”窗口中,指定新项目的名称和位置。
- 单击创建。
在本文的后续部分中,我们将使用该项目来说明 .NET Core 中的并行编程。
.NET Core 中的并发性和并行性
并发性和并行性是 .NET 和 .NET Core 中的两个关键概念。尽管它们看起来相同,但它们之间存在细微差别。
考虑必须由应用程序执行的两个任务 T1 和 T2。如果一个任务处于执行状态而另一个任务等待轮到它,则这两个任务处于并发执行状态。结果,其中一项任务先于另一项完成。相比之下,如果两个任务同时执行,则这两个任务是并行执行的。要实现任务并行,程序必须运行在多核 CPU 上。
.NET Core 中的 Parallel.For 和 Parallel.ForEach
Parallel.For
循环执行可以并行运行的迭代。你可以监视甚至操纵循环的状态。Parallel.For
循环就像 for
循环,只是它允许迭代在多个线程中并行运行。
Parallel.ForEach
方法将要完成的工作拆分为多个任务,每个任务对应集合中的每一项。Parallel.ForEach
类似于 C# 中的 foreach
循环,除了 foreach
循环在单个线程上运行并且按顺序进行处理,而 Parallel.ForEach
循环在多个线程上运行并且处理以并行方式进行。
C# 中的 Parallel.ForEach 与 foreach
考虑下面的方法,它接受一个整数作为参数,如果它是质数则返回 true。
static bool IsPrime(int integer)
{
if (integer <= 1) return false;
if (integer == 2) return true;
var limit = Math.Ceiling(Math.Sqrt(integer));
for (int i = 2; i <= limit; ++i)
if (integer % i == 0)
return false;
return true;
}
我们现在将利用 ConcurrentDictionary
来存储素数和托管线程 ID。由于两个范围之间的质数是唯一的,我们可以将它们用作键,将托管线程 ID 用作值。
.NET 中的并发集合包含在 System.Collections.Concurrent
命名空间内,并提供集合类的无锁和线程安全实现。ConcurrentDictionary
类包含在 System.Collections.Concurrent
命名空间内,代表一个线程安全的字典。
以下两个方法均使用 IsPrime
方法来检查整数是否为素数,将素数和托管线程 ID 存储在 ConcurrentDictionary
的实例中,然后返回该实例。第一种方法使用并发,第二种方法使用并行。
private static ConcurrentDictionary<int, int>
GetPrimeNumbersConcurrent(IList<int> numbers)
{
var primes = new ConcurrentDictionary<int, int>();
foreach (var number in numbers)
{
if(IsPrime(number))
{
primes.TryAdd(number,
Thread.CurrentThread.ManagedThreadId);
}
}
return primes;
}
private static ConcurrentDictionary<int, int>
GetPrimeNumbersParallel(IList<int> numbers)
{
var primes = new ConcurrentDictionary<int, int>();
Parallel.ForEach(numbers, number =>
{
if (IsPrime(number))
{
primes.TryAdd(number,
Thread.CurrentThread.ManagedThreadId);
}
});
return primes;
}
C# 中的并发与并行示例
以下代码片段说明了如何调用 GetPrimeNumbersConcurrent
方法来检索 1 到 100 之间的所有质数以及托管线程 ID。
static void Main(string[] args)
{
var numbers = Enumerable.Range(0, 100).ToList();
var result = GetPrimeNumbersConcurrent(numbers);
foreach(var number in result)
{
Console.WriteLine($"Prime Number:
{string.Format("{0:0000}",number.Key)},
Managed Thread Id: {number.Value}");
}
Console.Read();
}
当你执行上面的程序时,你应该看到如图 1 所示的输出:
图1
如你所见,托管线程 ID 在每种情况下都是相同的,因为我们在此示例中使用了并发。现在让我们看看使用线程并行时输出会是什么样子。以下代码片段说明了如何使用并行性检索 1 到 100 之间的素数。
static void Main(string[] args)
{
var numbers = Enumerable.Range(0, 100).ToList();
var result = GetPrimeNumbersParallel(numbers);
foreach(var number in result)
{
Console.WriteLine($"Prime Number:
{string.Format("{0:0000}",number.Key)},
Managed Thread Id: {number.Value}");
}
Console.Read();
}
当你执行上面的程序时,输出应该类似于图 2 所示:
图2
正如你在这里看到的,因为我们使用了 Parallel.ForEach
,所以创建了多个线程,因此托管线程 ID 是不同的。
限制C#中的并行度
并行度是一个无符号整数,表示你的查询在执行时应利用的最大处理器数。换句话说,并行度是一个整数,表示将在同一时间点执行以处理查询的最大任务数。
默认情况下,Parallel.For
和 Parallel.ForEach
方法对衍生任务的数量没有限制。因此,在上面显示的 GetPrimeNumbersParallel
方法中,程序尝试使用系统中的所有可用线程。
你可以利用 MaxDegreeOfParallelism
属性来限制衍生任务的数量(每个 Parallel 类的 ParallelOptions 实例)。如果 MaxDegreeOfParallelism
设置为 -1,则并发运行的任务数没有限制。
以下代码片段显示了如何设置 MaxDegreeOfParallelism
以使用最多 75% 的系统资源。
new ParallelOptions
{
MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0))
};
请注意,在上面的代码片段中,我们将处理器数量乘以二,因为每个处理器包含两个内核。以下是 GetPrimeNumbersParallel
方法的完整更新代码,供你参考:
private static ConcurrentDictionary<int, int> GetPrimeNumbersParallel(IList<int> numbers)
{
var primes = new ConcurrentDictionary<int, int>();
Parallel.ForEach(numbers, number =>
{
new ParallelOptions
{
MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0))
};
if (IsPrime(number))
{
primes.TryAdd(number,
Thread.CurrentThread.ManagedThreadId);
}
});
return primes;
}
确定 C# 中的并行循环是否完成
请注意,Parallel.For
和 Parallel.ForEach
都返回一个 ParallelLoopResult
实例,该实例可用于确定并行循环是否已完成执行。以下代码片段显示了如何使用 ParallelLoopResult
。
ParallelLoopResult parallelLoopResult = Parallel.ForEach(numbers, number =>
{
new ParallelOptions
{
MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling(
(Environment.ProcessorCount * 0.75) * 2.0))
};
if (IsPrime(number))
{
primes.TryAdd(number, Thread.CurrentThread.ManagedThreadId);
}
});
Console.WriteLine("IsCompleted: {0}", parallelLoopResult.IsCompleted);
要在非泛型集合中使用 Parallel.ForEach
,你应该利用 Enumerable.Cast
扩展方法将集合转换为泛型集合,如下面的代码片段所示。
Parallel.ForEach(nonGenericCollection.Cast<object>(),
currentElement =>
{
});
最后一点,不要假设 Parallel.For
或 Parallel.ForEach
的迭代将始终并行执行。你还应该注意线程亲和性问题。你可以阅读有关任务并行性的潜在缺陷。
总结
本文讨论了如何在C#中使用Parallel.For
和Parallel.ForEach
的问题。通过本文的学习,你应该了解了C#中Parallel.For
和Parallel.ForEach
的使用方法及注意问题。
相关文章
- 站长推荐