Um encontro da teoria com a prática na ciência da computação

Em cursos de exatas, como Ciência da Computação ou Sistemas de Informação, é comum encontrar alunos e ex-alunos efetuando diversas críticas sobre alguma teoria específica, que tenha demandado alto esforço para ser dominada. As maiores críticas normalmente são direcionadas a aplicação prática desta teoria, ou seja, é comum ouvir frases como: “por que sofrer com essa teoria se nunca vou precisar disso na prática?”. Fazendo uma analogia com este pensamento, confesso que, nos meus tempos de graduação em Engenharia de Computação, possuía certa dificuldade para entender o motivo de este curso incluir disciplinas envolvendo química, até que um dia fui escalado para apresentar um seminário sobre o processo de dopagem de semicondutores para fabricação de chips, onde fui obrigado a aprender sobre ligações químicas e consequentemente, reconhecer meu erro. Contudo, neste post, a intenção é apresentar um cenário mais prático, vivenciado em uma das empresas que trabalhei. Até hoje utilizo a experiência que será relatada abaixo como referência para as turmas da disciplina “Sistemas Operacionais”.

Certa vez, um dos meus empregadores precisava de um sistema que efetuasse uma busca de clientes, onde esta seria parametrizada por alguns filtros, como estado, cidade, bairro, etc.. . Após o retorno dos clientes filtrados, o sistema deveria disponibilizar um botão para gerar um mapa onde, ao ser clicado, este seria gerado no estilo Google Maps, marcando todos os clientes filtrados em sua exata localização. Para ilustrar melhor o problema, o mapa mostrado por este sistema era semelhante ao da figura abaixo, contudo, para cada cliente retornado no filtro, seria mostrado um ponto vermelho no local onde o cliente se encontra, ao invés de um único ponto, como mostra a figura. Aliado a isso, ao colocar o cursor do mouse sobre este cliente, seria mostrado algumas informações como Curva ABC em que este se encontrava, compras no mês corrente, etc… Com isso, foi contratado uma empresa terceira para desenvolver o sistema em Java, e outra para nos fornecer as coordenadas dos clientes mediante Web Service, ou seja, o endereço seria enviado por parâmetro e a Web Service retornaria a latitude e longitude deste.

Após a entrega do projeto mediante uma das empresas terceiras, deu-se início aos testes. Ao aplicar um filtro marcando apenas a cidade de Uberlândia, foram retornados cerca de mil clientes. Após clicar no botão para gerar o mapa, o sistema gastou cerca de meia hora para mostrá-lo com todos os mil pontos, referente aos clientes desta cidade. Obviamente, a primeira reação foi de incredulidade, pois poderiam ser aplicados outros filtros, como a cidade de São Paulo, ou o Distrito Federal, que gastariam um tempo consideravelmente maior para serem gerados. As funcionalidades pedidas no sistema, como dados apresentados no mapa, aplicação de filtros, layout, estavam absolutamente perfeitos em relação ao que foi acordado, o único problema era o desempenho, que inviabilizava o projeto.

Ao conferir o código fonte, foi constatado que a lentidão se encontrava em uma parte semelhante ao trecho abaixo:


List clientes = clienteDao.getClientesByParams(filtros);
for (Cliente cliente: clientes) {
     cliente.setCoordenadas(WebService.getCoordenadas(cliente));
}

Baseado no código fonte acima, pode-se constatar que a lentidão não estava na geração do mapa, ou na marcação dos pontos, mas sim na busca das coordenadas pela WebService. Cada uma das iterações deste loop gastava cerca de 2 segundos, ou seja, o tempo para gerar o mapa era linearmente proporcional a quantidade de clientes da lista. Caso o filtro retornasse poucos clientes, o mapa era gerado rapidamente, contudo para filtros mais abrangentes, o tempo para gerar era longo. A primeira reação ao ver este código fonte é a de questionamento do porque este ser sequencial, ao invés de paralelo, ou seja, multithread. Por que enviar apenas uma requisição por vez, sendo que poderiam ser enviadas diversas em paralelo? Apesar de óbvio, infelizmente o conceito de programação multithread não é tão difundido quanto imaginamos. Já aconteceram casos em que, empresas que venciam cotações pediam para retirar a parte multithread do projeto, com desculpas de que não haveria tantos ganhos de performance. Baseado no que foi dito, o código fonte foi modificado para algo abaixo:


List clientes = clienteDao.getClientesByParams(filtros);
List coordenadas = criaListaCoordenadas(clientes);
for (Coordenada coordenada: coordenadas) {
     Thread thread = new Thread(coordenada);
     thread.start();
}
public class Coordenada implements Runnable {
     private Cliente cliente;
     public Coordenada(Cliente cliente) {
         this.cliente = cliente;
     }
     public Cliente getCliente() {
         return cliente;
     }
     public void run() {
         cliente.setCoordenadas(WebService.getCoordenadas(cliente));
     }
}

Nesta mudança, cada objeto da classe Coordenada será uma thread, e para cada cliente retornado o filtro, será criado um objeto desta classe, associando-o ao cliente. Ao executar o projeto com estas modificações, sistema gerou várias mensagens de erro, uma para cada thread, alegando estouro de requisições. Ao analisar o log, foi constatado que, a empresa terceira que fornecia as coordenadas não suportava o volume de requisições simultâneas demandadas, ou seja, aceitava apenas algumas requisições simultâneas. Então, chegou-se a conclusão que era necessário criar uma espécie de funil, ou seja, o sistema até poderia disparar várias threads simultaneamente, mas apenas algumas destas poderiam estar simultaneamente efetuando requisições no servidor. Para tal atividade, foi utilizado um conceito ensinado nas disciplinas envolvendo Sistemas Operacionais, chamado semáforo. Para alguns alunos de cursos voltados à Computação, juntamente com grafos e árvores, semáforo é dos conceitos mais difíceis para se aprender e aplicar na prática. Mas, neste cenário, resolveu perfeitamente o problema proposto, como será mostrado no código fonte abaixo.


List clientes = clienteDao.getClientesByParams(filtros);
List coordenadas = criaListaCoordenadas(clientes);
Integer numeroRequisicoesSimultaneas = 50;
Semaforo semaforo = new Semaforo(numeroRequisicoesSimultaneas);
for (Coordenada coordenada: coordenadas) {
     Thread thread = new Thread(coordenada);
     thread.start();
}
public class Coordenada implements Runnable {
     private Cliente cliente;
     private Semaforo semaforo;
     public Coordenada(Cliente cliente,Semaforo semaforo) {
         this.cliente = cliente;
         this.semaforo = semaforo;
     }
     public Cliente getCliente() {
         return cliente;
     }
     public void run() {
        semaforo.down();
        cliente.setCoordenadas(WebService.getCoordenadas(cliente));
        semaforo.up();
     }
}

public class Semaforo {
     private int numeroThreadsSimultaneas;
     public Semaforo(int numeroThreadsSimultaneas) {
         this.numeroThreadsSimultaneas = numeroThreadsSimultaneas;
     }
     public void down() {
         synchronized (this) {
             while (numeroThreadsSimultaneas <= 0) {
                try {
                     wait();
                 } catch (InterruptedException ex) {
                }
            }
            numeroThreadsSimultaneas–;
        }
     }
     public void up() {
         synchronized (this) {
             count++;
             if (count == 1) {
                notifyAll();
             }
         }
     }
}

Analisando o código fonte acima, a classe Semaforo foi inicializada com o valor 50, que diz respeito ao número de requisições simultâneas que o sistema permitirá. Para cada Thread que for iniciada, quando o método down() for invocado, o semáforo decrementará em 1, e quando o método up() for invocado, o semáforo será incrementado em 1. Quando o valor do semáforo chegar em 0, o “funil” será fechado, ou seja, caso seja invocado o método down(), a thread que estará invocando este método será bloqueada. Já com valor 0 e o método up(), as threads que foram bloqueadas serão acordadas para tentarem novamente passar pelo “funil” e, posteriormente, invocar a WebService. Desta forma, é possível garantir que apenas 50 requisições estarão simultaneamente requisitando as coordenadas pela WebService. Com isso, utilizando o semáforo e limitando em 50 requisições simultâneas, a mesma busca por Uberlândia que antes demorava meia hora, após a aplicação deste conceito, o tempo foi decrementado para cerca de 30 segundos, viabilizando o projeto e deixando o cliente satisfeito.

Concluindo, foi possível demonstrar que, com um pouco de domínio do conceito, e uma certa capacidade de associá-lo com os problemas enfrentados no mercado de trabalho, é possível resolver problemas práticos relativamente mais complexos do que o comum, e quem sabe com isso, obter algum destaque e promoção, além da imprescindível satisfação pessoal em conseguir provar para si mesmo que o aprendizado valeu a pena, tornando-o alguém que faz alguma diferença.

About CarlosEduardoXP

Especialista em desenvolvimento de Sistemas Distribuídos, sempre aplicando boas práticas e padrões difundidos na comunidade. Auto didata, fanático por refatoração e performance, sempre buscando reutilização e testes automatizados cada vez mais eficazes.
This entry was posted in Software Development. Bookmark the permalink.

11 Responses to Um encontro da teoria com a prática na ciência da computação

  1. rodolfo4j says:

    Muito bom o post Cadu! Espero que muitos estudantes possam entender que é justamente essa base teórica que diferencia um bom profissional. E para quem já está na área, possa correr atrás do prejuízo.

  2. rodolfo4j says:

    Só uma pergunta, não seria possível que o WebService fosse alterado para receber uma lista de clientes e retornasse uma lista de coordenadas, podendo executar a operação em uma única requisição ?

    • Concordo Rodolfo, se fosse feito dessa forma facilitaria bastante, porém o jar entregue pela empresa terceira com as interfaces da Web Service permitiam que fosse consultado apenas uma coordenada de cada vez.

  3. Marcos Lopes says:

    MUITO BOM CADU!!!

  4. Leon Parreira Garcia says:

    Excelente seu relato Cadu!
    Infelizmente ainda lidamos com uma erva-daninha no mercado chamada “comodismo”.

  5. Ecson says:

    Excelente Post Cadu! Sem a teoria não existi base nem para começar. A solução foi fantástica, incrível! No meio do texto pensei! A thread vai resolve o problema, mas ainda precisou de mais conceito e esforço. Parabéns.

    • Obrigado pelo elogio Ecson, eu sempre gostei muito de semáforos, e quando vi este problema, não perdi a chance de implementá-lo e evangelizar a técnica (como se fosse necessário, já que há anos o mutex dos Sos são garantidos com semáforo).. hehe

      Grande abraço

  6. Mario Mendes says:

    Muito bom post carlão, teoria e prática muito bem aplicados.

  7. Pingback: Um encontro da teoria com a prática na ciência da computação – parte 2 | carloseduardoxp

  8. Olá Carlos,

    Achei seu blog aqui navegando na net.
    Gostei muito da solução e acho ótimo ter por perto profissionais que gostem de pensar para resolver problemas que as vezes subestimamos.

    Fiquei curioso com um detalhe no código e percebi que tem algo errado aí. Quando você “instânciou” a Thread, você passou o semáforo no construtor e a java.lang.Thread não recebe um Runnable, Object …

    http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html

    Como você deve ter copiado e editado o código, deve ter cometido o erro quando copiou a criação da instância da sua Coordenada, “né”?

    Ah, gostei muito também da escrita simples que facilitou a leitura nessa tarde de sexta-feira.
    🙂

    Abraços.

    • Olá Fábio, obrigado pelo elogio, serve de combustível para continuarmos escrevendo, especialmente quando diz respeito a problemas do dia-a-dia que, como sabe, não encontramos tão facilmente mastigados em registros bibliográficos como gostaríamos.

      Sobre o erro, tem toda a razão, nem foi ctrl + c ctrl + v. Na verdade, eu simplifiquei o código do artigo porque neste trecho existem outros detalhes que não são inerentes ao problema, por isso escapou esta falha🙂 .

      Obrigado pela dica,

      Abraços

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s