Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Por que concorrência e paralelismo?

Como veremos ao longo do livro, programação concorrente e paralela leva a bugs que são mais difíceis de lidar do que aqueles vistos na programação sequencial. Portanto, uma pergunta legítima é: “Por que eu iria escrever programas concorrentes e paralelos em primeiro lugar?”.

A resposta é que programas concorrentes e paralelos melhoram métricas que são importantes para uma aplicação ou sistema. Dentre essas métricas, destacamos as mais relevantes: latência, vazão e responsividade.

Latência (Latency)

Latência é o tempo passado entre uma requisição e o início da resposta correspondente. Formalmente, para uma requisição no tempo \( t_0 \) cuja resposta começa a chegar no tempo \( t_1 \), a latência \( L \) é:

\[ L = t_1 - t_0 \]

Para ver como a concorrência pode melhorar a latência, considere um servidor HTTP sem uso de concorrência:

  1. No tempo \( t_0 = 0\ \text{ms} \), uma requisição \( R_1 \) chega ao servidor HTTP. Para respondê-la, o servidor precisa consultar informações no banco de dados.
  2. A consulta ao banco de dados leva 8 ms.
  3. No instante \( t_1 = 3\ \text{ms} \), uma outra requisição \( R_2 \) é recebida. Esta, no entanto, não requer consulta ao banco de dados. Apesar disso, o servidor está bloqueado aguardando a conclusão da consulta para \( R_1 \). A CPU está ociosa.
  4. Em \( t_2 = 8\ \text{ms} \), a consulta ao banco de \( R_1 \) é concluída e o servidor emite a resposta.
  5. O servidor analisa \( R_2 \), monta a resposta e a envia em \( t_3 = 10
    \text{ms} \).

Seja \( L(R) \) a latência de uma requisição \( R \). Temos que \( L(R_1) = 8\ \text{ms} \) e \( L(R_2) = 7\ \text{ms} \).

Agora, considere um servidor HTTP com uso de concorrência:

  1. No tempo \( t_0 = 0\ \text{ms} \), uma requisição \( R_1 \) chega ao servidor HTTP. Para respondê-la, o servidor precisa consultar informações no banco de dados.
  2. A consulta ao banco de dados leva 8 ms. O servidor cria uma tarefa que fica aguardando até receber uma resposta do banco, ficando livre para atender outras requisições.
  3. No instante \( t_1 = 3\ \text{ms} \), uma outra requisição \( R_2 \) é recebida. Esta, no entanto, não requer consulta ao banco de dados. Como o processo principal do servidor está livre, ele recebe a nova requisição e cria uma nova tarefa para atendê-la.
  4. Em \( t_2 = 5\ \text{ms} \), a tarefa para \( R_2 \) finaliza e o servidor envia uma resposta.
  5. Em \( t_2 = 8\ \text{ms} \), a consulta ao banco de \( R_1 \) é concluída e o servidor emite a resposta.

Agora temos que \( L(R_1) = 8\ \text{ms} \) e \( L(R_2) = 5 - 3 = 2
\text{ms} \). Portanto, houve melhoria na latência de \( R_2 \), ainda que só tenhamos introduzido concorrência e não necessariamente paralelismo.

Vazão (Throughput)

Vazão é a taxa que um sistema completa trabalho, num dado intervalo de tempo. É a quantidade de operações (ou dados) processados por unidade de tempo.

Para \( N \) operações completadas num intervalo de tempo \( \Delta t \), a vazão \( V \) é:

\[ V = \frac{N}{\Delta t} \]

Considere um programa que deve redimensionar 100 imagens, onde cada redimensionamento leva 10 ms de computação na CPU, seguidos de 40 ms de I/O de disco para escrever o resultado.

Versão sequencial/serial: cada imagem leva 50 ms fim-a-fim e a próxima imagem não pode começar a ser processada até que a anterior seja completamente escrita no disco. Portanto, a vazão é de:

\[ V_{serial} = \frac{100}{100 \cdot 50\ \text{ms}} = \frac{100}{5000\ \text{ms}} = 20\ \text{imagens/s} \]

Note que durante 80% do tempo de relógio decorrido, a CPU fica ociosa.

Versão concorrente: Enquanto a imagem \( n \) está sendo escrita no disco (I/O, portanto a CPU não é utilizada), a CPU pode começar a redimensionar a imagem \( n + 1 \):

Nesse caso, a vazão é de: \[ V_{concorrente} = \frac{100}{10 + 100 * 40\ \text{ms}} = \frac{100}{4010\ \text{ms}} \approx 24.9\ \text{imagens/s} \]

representando um aumento de quase 25% sobre o inicial.

Responsividade

Responsividade é a propriedade de um sistema pela qual ele reconhece e reage a interações e mudanças, provendo feedback num intervalo de tempo adequado. Não é medida matematicamente, mas sim perceptualmente.

Considere uma aplicação desktop, como um editor de texto (Word, LibreOffice Writer, etc). A funcionalidade de “Exportar PDF” em um documento grande envolve uma série de etapas: inclusão de fontes, compressão de imagens, resolução de layout, etc. Isso custa vários segundos de computação.

Versão sequencial: A exportação roda na mesma tarefa (na mesma thread, como veremos no próximo capítulo) que processa os eventos de interação do usuário e atualiza a interface gráfica. Enquanto o PDF está sendo renderizado, a aplicação não pode processar quaisquer eventos de interação, fazendo com que:

  • A janela para de atualizar a interface gráfica
  • Os cliques começam a se enfileirar silenciosamente
  • O cursor congela
  • O sistema operacional muda a cor da janela da aplicação para cinza, indicando que ela “Não está respondendo”
  • O usuário não sabe se a exportação está em processamento, travou ou deu erro, fazendo com que ele clique no botão novamente ou mate forçadamente a aplicação

Versão concorrente: A aplicação delega a renderização do PDF para uma tarefa em background e imediatamente retorna o controle para a tarefa responsável pela interface de usuário (UI). Agora, enquanto o PDF está sendo renderizado:

  • A tarefa de UI instantaneamente reconhece a ação do usuário — um indicador de progresso (barra de progresso, spinner ou mensagem de status) é exibido.
  • O usuário pode continuar interagindo com o documento e outros menus. A aplicação permanece viva, recebendo entrada.
  • A tarefa em background continuamente reporta progresso, de forma que o usuário continuamente veja o resultado progredindo.
  • Quando a renderização finaliza, a tarefa em background sinaliza para a tarefa de UI, que exibe uma notificação de “PDF salvo”.

Essa é uma demonstração de um padrão de concorrência muito comum: a concorrência desacopla uma computação custosa do ciclo de feedback exposto ao usuário.