C# Paralelismo

Neste artigo, menciono exemplos de códigos feitos em C#, onde tento explorar um pouco mais da computação paralela de tarefas.

Falando a grosso modo, basicamente, o programa cliente quando executado, começa em uma única thread  (“main” thread),  segmento este, criado automaticamente pelo CLR e o Windows.

Quando usamos o paralelismo, este segmento (thread) é dividido em vários outros segmentos (threads), criando segmentos adicionais, desta forma, aproveitamos melhor o poder do processamento da máquina e fazemos o programa fluir mais rapidamente.

Vamos na prática entender melhor o seu funcionamento.

class TarefaTeste
{
 static void Main()
 {
     Thread t = new Thread (EscreveLetra); // INICIAMOS UM NOVO SEGMENTO
     t.Start(); // EXECUTANDO...

// Ao mesmo tempo, vamos fazer alguma coisa no segmento principal.
for (int i = 0; i < 1000; i++) Console.Write ("1");

}

static void EscreveLetra()
{
for (int i = 0; i < 1000; i++) Console.Write ("a");

}
}

O thread principal cria uma nova thread t em que é executado um método que imprime repetidamente o caráter “a”. Simultaneamente, o segmento principal imprime repetidamente o número 1.

No próximo código, vejamos um exemplo onde notamos que o CLR atribui para cada Thread uma pilha de memória individual mantendo desta forma uma cópia da variável “z” para cada thread em execução.

static void Main() 
{
  new Thread(Imp).Start();      // Chamada do método Imp() em um novo segmento
  Imp();                         // Chamada Imp()na thread principal
}
 
static void Imp()
{
  // declarar e usar uma variável local - "z"
  for (int z = 0; z < 5; z++) Console.Write('Z');
}

No exemplo acima, definimos um método com uma variável local, em seguida, chamamos o método simultaneamente no segmento principal.

Abaixo exemplifico uma lógica viável para lidar com uma situação similar.

class TarefaTest
{
  bool feito;
 
  static void Main()
  {
    TarefaTest t = new TarefaTest();   // cria uma instancia comum
    new Thread (t.Imp).Start();
    t.Imp();
  }
 
  // Note que Imp é agora um método de instância
  void Imp() 
  {
     if (!feito) { feito = true; Console.WriteLine("pronto"); }
  }
}

Como os dois segmentos chamam o método Imp() na mesma TarefaTest, note que eles compartilham a variável “feito”. Isso resulta em “pronto”  impresso uma vez… ao invés de duas vezes.

Falando de variáveis estáticas, as  mesmas oferecem uma outra maneira de compartilhar dados entre as threads. Veja um exemplo similar de código logo abaixo:

class TarefaTest
{
  static bool feito;
 
  static void Main()
  {
    new Thread (Imp).Start();
    Imp();
  }
  
  static void Imp() 
  {
     if (!feito) { feito = true; Console.WriteLine("pronto"); }
  }
}

Ambos os exemplos ilustram um outro conceito fundamental: o de thread safety (ou melhor, a falta dela!).

Note que a saída é na verdade indeterminada, é possível que a variável “feito” poderia ser impressa duas vezes, e este risco aumenta se você trocar a ordem das declarações do método Imp, veja abaixo um exemplo:

 
  static void Imp() 
  {
     if (!feito) { Console.WriteLine("pronto"); feito = true;  }
  }

O problema é que uma thread enquanto avalia a condição if, a outra poderia já estar executando o WriteLine, isso antes que tivesse a chance de definir o feito para true.

A solução seria obter um bloqueio exclusivo ao ler e escrever para a variável comum.

Veja o exemplo abaixo:

class TarefaTest
{
  static bool feito;
  static readonly object protege = new object() 
  static void Main()
  {
    new Thread (Imp).Start();
    Imp();
  }
  
  static void Imp() 
  {
     lock (protege)
     {
       if (!feito) { Console.WriteLine("pronto"); feito = true;}
     }
  }
}

No exemplo acima, quando duas threads simultaneamente enfrentar um bloqueio (neste caso, protege) , uma thread espera, até que o bloqueio se torna disponível. Neste caso, ele garante apenas que uma thread execute a seção do código protegida.

A seção do código protegida é denominada thread-safe.

Hoje em dia a maior complexidade em programação multithreading é a questão do compartilhamento de campos (variáveis).

No exemplo abaixo, mostro como fazer uma thread aguardar a finalização de outra.

class TarefaTest
{
 
  static void Main()
  {
    Thread t = new Thread(Imp);
    t.Start();
    t.Join();
    Console.WriteLine("A thread t acabou...!!");
  }
   
  static void Imp() 
  {
      for (int z = 0; z < 5; z++) Console.Write('Z');
  }
}

Existe também uma forma de criar um tempo limite, seja em milisegundos ou como um TimeSpan. Em seguida, retorna true se a thread terminou, ou false se esgotou..

No próximo artigo mostro como faremos isso..

 

 

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.