Desafio da Semana #13


Por: Roberto Alexis Farah

Olá pessoal! Estou em débito com meus leitores, pois já se passaram semanas e ainda não tive tempo de publicar um novo desafio do mês, ops..., digo, da semana.

No desafio de hoje começarei a abordar um tópico que ainda não foi parte dos desafios: problemas relacionados a multithreading.

Particularmente com Visual Basic .NET e C# é bastante fácil de se usar threads em aplicações, entretanto, sincronizar o acesso dessas threads a recursos compartilhados é outra história.

Muitas vezes quando trabalhando em incidentes de aplicações com multithread e sintomas usualmente classificados como hang ou performance, recebo perguntas de clientes como:

- Devo fazer o lock menos ou mais granular?

- Qual o melhor mecanismo de sincronização para usar em determinada situação?

- Porque minha aplicação roda com boa performance usando 25 threads, roda com mais performance com 50 threads mas roda lentamente com 100 threads?

- Porque minha aplicação multithread degrada a performance lentamente?

- Quando preciso e quando não preciso me preocupar com acesso sincronizado de múltiplas threads?

Portanto, começo por esse desafio a escrever sobre multithreading, continuando a contribuição para se evitar o uso de POG (Programação Orientada a Gambiarras) (isso é bem engraçado J )em qualquer contexto, principalmente em programação multithreading.

CENÁRIO

Temos uma aplicação C# abaixo, feita com .NET Framework 2.0 que pode ser facilmente convertida para VB.NET e o mesmo sintoma ocorrerá.

A aplicação usa um método em modo compartilhado que incrementa uma variável, entretanto, assuma que a operação poderia ser outra coisa diferente de incrementar uma variável.

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

namespace ThreadBug

{

          public class Bacteria

          {

                   private static int _bacteriaCount;

                  

                   private static void DoSomething()

                   {

                             Console.WriteLine("DoSomething chamado por {0} no momento {1}",

          Thread.CurrentThread.ManagedThreadId,

                                                                   DateTime.Now.ToLongTimeString());

                             // Assuma que o método poderia fazer outra operação que não incrementar uma variável!

                             _bacteriaCount++;

                            

                             Thread.Sleep(3000);

                   }

                  

                   public Bacteria()

                   {

                             lock(this)

                             {

                                      DoSomething();

                             }

                   }

          }

         

          public class SpecializedBacteria : Bacteria

          {

          }

         

         

          class Program

          {

                   private static void Worker1()

                   {

                             Console.WriteLine("Worker1 executando thread {0}",

                                                                   Thread.CurrentThread.ManagedThreadId);

                                                                            

                             Bacteria bac = new Bacteria();

                   }

                   private static void Worker2()

                   {

                             Console.WriteLine("Worker2 executando thread {0}",

                                                                   Thread.CurrentThread.ManagedThreadId);

                             SpecializedBacteria bac = new SpecializedBacteria();

                   }

                  

                   [STAThread]

                   static void Main(string[] args)

                   {

                             Thread thread1 = new Thread(new ThreadStart(Worker1));

                             Thread thread2 = new Thread(new ThreadStart(Worker2));

                            

                             Console.WriteLine("Iniciando as threads em {0}",

                                                DateTime.Now.ToLongTimeString());

                             thread1.Start();

                             thread2.Start();

                            

                             Thread.Sleep(4000);

                   }

          }

}

SINTOMA

As threads acessam, ao mesmo tempo, o método DoSomething(). O correto é que apenas uma thread, em determinado momento, acessar o método estático.

OBJETIVO

Identifique o PROBLEMA ocasionando a falha na sincronização das threads e proponha uma SOLUÇÃO.

Nota: Assuma que o método poderia fazer uma operação diferente de incrementar uma variável, portanto, a solução deve contemplar essa possibilidade.