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.