Entendo processamento paralelo no C# com o método Parallel.ForEach()
Com a evolução dos processadores, foi notado que apesar de ser possível aumentar a quantidade de informação processada de uma vez, diminuir o tempo necessário nesse processo é outra história, sendo necessária outra abordagem para criar processadores mais poderosos e rápidos e a alternativa foi o processamento paralelo ao dividir a CPU em múltiplos núcleos de igual capacidade. A classe Parallel do C# está no pacote de Threading que foca na utilização de múltiplos núcleos e é examente o que o método da vez faz. O método Parallel.ForEach basicamente recebe um vetor e um metódo que será executado em cada elemento do vetor, a diferença do convencional é que isso é feito em um núcleo diferente do principal.
O que é o processamento paralelo
Como o nome sugere, o processamento paralelo caracteriza uma máquina de executar mais de um processo simultaneamente, no caso dos nossos computadores, ele é implementado através de vários "núcleos" iguais dentro do processador. Quanto mais núcleos uma CPU tiver mais processos ele poderá executar simultaneamente.
Em uma comparação rápida, um i9 de 14° geração possui 24 núcleos enquanto um i9 de 11° geração possui apenas 8, nesse exemplo o processador de 14° geração pode executar 3 vezes mais processos simultâneos do que o de 11°.
Fonte: Intel - Processadores i9
Como C# lida com o paralelismo
Segundo a própria Microsoft, a .NET desde da sua versão Framework 4 disponibiliza um conjunto de bibliotecas de classes e ferramentas de diagnósticos retirando a necessidade de manipular os núcleos em linguagens de baixo nível e possiblitando o desenvolvimento em linguagem natural.
Fonte: Microsoft Learn - Programação Paralela
Como funciona o paralelismo na prática
Uma classes desponíveis para utilizar do paralelismo é a classe estática Parallel que contém um conjunto de métodos para executar _regiões de código_ e _loops._ Para entender seu funcionamento eu irei usar o método ForEach().
O método ForEach() é um método públic e estático que utiliza basicamente de um IEnumerable e de uma Action para realizar um loop foreach em outro núcleo sincronizado ao principal. Para exemplificar o funcionamento desse método vou criar a classe Num para segurar um inteiro que será alterado durante o loop.
public class Num {
public int value;
public Num(int value) {
this.value = value;
}
public override string ToString() {
return value.ToString();
}
}
Agora, no método principal vou criar um vetor de 50 números de 1 até 50.
public static void Main()
{
Num[] numbers = new Num[500]; // Vetor
for (int i=0; i<500; i++) {
numbers[i] = new Num(i + 1);
}
// Imprimindo todos os valores do vetor
Console.WriteLine(String.Join<Num>(", ", numbers));
// Saída: 1, 2, 3, 4, 5, ...
// Restante do código
}
Com o vetor podemos elevar todos os valores por 2 e obter o quadrado deles através do método Prallel.ForEach(). O método funciona cria um loop igual à palavra-chave foreach.
public static void Main()
{
// Restante do código
Parallel.ForEach<Num>(numbers, n => {
n.value *= n.value;
});
Console.WriteLine(String.Join<Num>(", ", numbers));
// Saída: 1, 4, 9, 16, 25, ...
}