Ciência da computação

Concorrência e paralelismo: diferenças e concorrência em um núcleo

A concorrência organiza várias tarefas alternando entre elas; o paralelismo executa várias ao mesmo tempo por meio do hardware. Aqui resumimos por que a concorrência funciona em um único núcleo e o significado de Swift Concurrency.

4 min de leitura
Imagem de capa de Concorrência e paralelismo: diferenças e concorrência em um núcleo

Concorrência (concurrency) e paralelismo (parallelism) são parecidos tanto em coreano quanto em inglês, por isso costumam ser confundidos.

Mesmo quando as CPUs tinham um único núcleo, os computadores reproduziam música enquanto editávamos documentos. Como uma máquina que fisicamente só consegue fazer uma coisa por vez podia fazer várias coisas «simultaneamente»?

Se você consegue responder a essa pergunta, já diferencia os dois conceitos. Se não, este artigo vai esclarecer exatamente esse ponto.

O mesmo vale para nomes como Swift Concurrency e a fila concorrente do GCD (Grand Central Dispatch). Essa distinção é a base para entendê-los corretamente.

Este é o resumo principal.

  1. Concorrência: estrutura lógica que alterna várias tarefas para «lidar com elas ao mesmo tempo».
  2. Paralelismo: várias tarefas são executadas fisicamente no mesmo instante. É uma questão de hardware.
  3. A concorrência é possível em um único núcleo. O paralelismo exige vários núcleos.
  4. A concorrência é criada pela estrutura do código; o paralelismo é o resultado de o hardware aproveitar essa estrutura.

Entendendo com uma única cafeteira

Imagine uma cafeteria com um único barista (um núcleo).

Dois pedidos chegam. Enquanto extrai o espresso do cliente A, o barista aquece o leite do cliente B; quando termina a extração, finaliza a bebida de A e volta para B. Os dois pedidos estão «em andamento ao mesmo tempo», mas, em qualquer instante, o barista faz apenas uma coisa.

Isso é concorrência: dividir as tarefas em partes menores, alterná-las e lidar com várias ao mesmo tempo.

Ao contratar outro barista (dois núcleos), os dois pedidos passam a ser preparados realmente no mesmo instante. Isso é paralelismo.

O importante é isto: mesmo com um barista, a «forma de operar alternando os pedidos» funciona; quando há dois, essa mesma forma se expande para uma execução física simultânea.


Levando isso para o computador

A multitarefa da época de um único núcleo era exatamente uma cafeteria com um barista.

O sistema operacional alternava a atribuição da CPU entre os processos em intervalos de dezenas de milissegundos (divisão de tempo). Por isso, para as pessoas, a reprodução de música e a edição de documentos pareciam acontecer ao mesmo tempo.

Diagrama comparando a divisão de tempo em um único núcleo com a execução simultânea em vários núcleos
Um único núcleo alterna as tarefas (divisão de tempo); vários núcleos executam no mesmo instante

Com vários núcleos, o paralelismo foi adicionado. Agora, quatro núcleos podem executar quatro threads realmente no mesmo instante.

Podemos resumir a relação assim.

  • Somente paralelismo, sem concorrência: tarefas independentes, uma por núcleo.
  • Concorrência sem paralelismo: divisão de tempo em um único núcleo.
  • Concorrência + paralelismo: a base da computação moderna. Vários núcleos executam as tarefas divididas.

O título de uma palestra famosa de Rob Pike, criador da linguagem Go, resume essa distinção em uma frase.

«Concorrência não é paralelismo (Concurrency is not parallelism).»

Ele diferenciou concorrência como «lidar com várias tarefas (dealing with)» e paralelismo como «fazer várias tarefas (doing)».


O significado do nome Swift Concurrency

Sob essa perspectiva, faz sentido a Apple ter chamado de Swift «Concurrency» o modelo async/await.

async/await, Task e actor são ferramentas para expressar «como dividir as tarefas, onde suspendê-las e em que ordem retomá-las». Em outras palavras, são recursos da linguagem para criar estruturas concorrentes.

O runtime e o escalonador do sistema decidem em quantos núcleos essa estrutura será executada em paralelo. O desenvolvedor declara a estrutura (concorrência), e o sistema fica responsável pela execução paralela.

async let a = fetchProfile()   // Declaração de uma estrutura que «pode avançar simultaneamente»
async let b = fetchFeed()
let result = try await (a, b)  // O sistema decide se será executado em paralelo

Mesmo criando 100 tarefas com TaskGroup, em um sistema com 8 núcleos, no máximo 8 rodam no mesmo instante. As demais são alternadas. Dentro de uma estrutura concorrente, o paralelismo é limitado pelo hardware.

Ilustração de um escalonador distribuindo 100 fichas de tarefas entre 8 slots de núcleo
Ao declarar uma estrutura concorrente, o sistema decide se haverá execução paralela

Em uma frase para a entrevista

«Concorrência é uma estrutura lógica que lida com várias tarefas alternando entre elas, enquanto paralelismo é a execução física de várias tarefas no mesmo instante. Por isso, a concorrência é possível em um único núcleo, mas o paralelismo não.»

Também vale preparar as perguntas de acompanhamento: «Como a multitarefa era possível em um único núcleo?» (divisão de tempo) e «O código concorrente sempre roda em paralelo?» (Não; o escalonador decide).


Resumo

  • A concorrência divide as tarefas, alterna entre elas e lida com várias ao mesmo tempo. É um conceito lógico.
  • O paralelismo executa várias tarefas fisicamente no mesmo instante. É um conceito de hardware.
  • A concorrência (divisão de tempo) é possível em um único núcleo, mas o paralelismo não.
  • Rob Pike: concorrência é «lidar» com várias tarefas; paralelismo é «fazê-las».
  • Swift Concurrency declara a estrutura concorrente; o sistema decide a execução paralela real.
  • Analogia da cafeteira: um barista alternando entre tarefas representa concorrência; dois trabalhando juntos representam paralelismo.