Concorrência x Paralelismo – Definição e diferenças

Descubra as diferenças entre concorrência e paralelismo e saiba qual é o melhor para suas necessidades de desenvolvimento de software.
14 min de leitura
Concurrency vs Parallelism blog image

Este blog discutirá a concorrência e o paralelismo em detalhes para ajudá-lo a escolher o melhor conceito para sua aplicação.

O que é concorrência?

Em termos simples, a concorrência é um conceito usado no desenvolvimento de software para lidar com várias tarefas simultaneamente. No entanto, em teoria, ela não executa todas as tarefas ao mesmo tempo. Em vez disso, permite que o sistema ou aplicativo gerencie várias tarefas simultaneamente, alternando rapidamente entre elas, criando uma ilusão de processamento paralelo. Esse processo também é conhecido como intercalação de tarefas.

Por exemplo, considere um servidor web que precisa lidar com várias solicitações de usuários.

  • O usuário 1 envia uma solicitação ao servidor para recuperar dados.
  • O usuário 2 envia uma solicitação ao servidor para fazer upload de um arquivo.
  • O usuário 3 envia uma solicitação ao servidor para recuperar imagens.

Sem a simultaneidade, cada usuário deve esperar até que a solicitação anterior seja atendida.

  • Etapa 1: a CPU começa a processar a solicitação de recuperação de dados na thread 1.
  • Etapa 2: enquanto o thread 1 aguarda o resultado, a CPU inicia o processo de upload do arquivo no thread 2.
  • Etapa 3: enquanto o thread 2 aguarda o upload do arquivo, a CPU inicia a recuperação de imagens no thread 3.
  • Etapa 4: Em seguida, a CPU alterna entre essas três threads com base na disponibilidade de recursos para concluir todas as três tarefas simultaneamente.
Example of 3 tasks running concurrently

Em comparação com a abordagem de execução síncrona, a abordagem de concorrência é muito mais rápida e extremamente útil para ambientes single-core, a fim de melhorar o tempo de resposta geral do sistema, a utilização de recursos e os recursos de rendimento do sistema. No entanto, a concorrência não se limita ao single-core; ela também pode ser implementada em ambientes multi-core.

Casos de uso da concorrência

  • Interfaces de usuário responsivas.
  • Servidores web.
  • Sistemas em tempo real.
  • Operações de rede e E/S.
  • Processamento em segundo plano.

Diferentes modelos de concorrência

Com a crescente complexidade e exigências das aplicações modernas, os desenvolvedores introduziram novos modelos de concorrência para resolver as deficiências da abordagem tradicional. Aqui estão alguns modelos de concorrência importantes e suas utilizações:

1. Multitarefa cooperativa

Nesse modelo, as tarefas voluntariamente cedem o controle ao agendador em pontos apropriados, permitindo que ele processe outras tarefas. Essa cedência geralmente ocorre quando a tarefa está ociosa ou aguardando operações de E/S. Esse é um dos modelos mais fáceis de implementar, pois a troca de contexto é gerenciada dentro do código do aplicativo.

Exemplos:

  • Sistemas embarcados leves
  • Versões antigas do Microsoft Windows (Windows 3.x)
  • Mac OS clássico

Aplicações no mundo real:

2. Multitarefa preemptiva

O sistema operacional ou o agendador de tempo de execução força as tarefas a parar e aloca tempo de CPU para outras tarefas com base em um algoritmo de agendamento. Esse modelo garante que todas as tarefas recebam uma parcela igual do tempo de CPU. No entanto, ele requer uma troca de contexto mais complexa.

Exemplos:

Aplicações no mundo real:

  • Sistemas operacionais modernos (Windows, macOS, Linux)
  • Servidores web.

3. Concorrência orientada a eventos

Neste modelo, as tarefas são divididas em pequenas operações não bloqueantes e enfileiradas em uma fila. Em seguida, elas recebem tarefas da fila, executam a ação necessária e passam para a próxima, mantendo o sistema interativo.

Exemplos:

  • Node.js (ambiente de execução JavaScript).
  • Padrão async/await do JavaScript.
  • Biblioteca asyncio do Python.

Aplicações no mundo real:

  • Servidores web como Node.js.
  • Aplicações de chat em tempo real.

4. Modelo de ator

Usa atores para enviar e receber mensagens de forma assíncrona. Cada ator processa uma mensagem por vez, evitando o estado compartilhado e reduzindo a necessidade de bloqueios.

Exemplos:

Aplicações no mundo real:

5. Programação reativa

Este modelo permite criar fluxos de dados (observáveis) e definir como eles devem ser processados (operadores) e como devem reagir (observadores). Ocorrem alterações nos dados ou eventos, que se propagam automaticamente pelos fluxos para todos os observadores inscritos. Essa abordagem facilita o gerenciamento de dados e eventos assíncronos, fornecendo uma maneira clara e declarativa de lidar com fluxos de dados complexos.

Exemplos:

Aplicações no mundo real:

  • Pipelines de processamento de dados em tempo real.
  • Interfaces de usuário interativas.
  • Aplicações que exigem tratamento de dados dinâmico e responsivo.

O que é paralelismo?

O paralelismo é outro conceito popular usado no desenvolvimento de software para lidar com várias tarefas simultaneamente. Ao contrário da concorrência, que cria a ilusão de processamento paralelo ao alternar rapidamente entre tarefas, o paralelismo realmente executa várias tarefas simultaneamente usando vários núcleos de CPU ou processadores. Envolve dividir tarefas maiores em subtarefas menores e independentes que podem ser executadas em paralelo. Esse processo é conhecido como decomposição de tarefas.

Por exemplo, considere um aplicativo de processamento de dados que gera relatórios após realizar análises e executar simulações. Sem paralelismo, isso será executado como uma grande tarefa, levando um tempo significativo para ser concluído. Mas, se você optar pelo paralelismo, a tarefa será concluída muito mais rapidamente por meio da decomposição de tarefas.

Veja como o paralelismo funciona:

  • Etapa 1: Divida a tarefa principal em subtarefas independentes. Essas subtarefas devem poder ser executadas sem esperar por entradas de outras tarefas. No entanto, se houver alguma dependência, você precisará programá-las adequadamente para garantir que sejam executadas na ordem correta. Neste exemplo, presumirei que não há dependências entre as subtarefas.
  • Subtarefa 1: Realizar análise de dados.
  • Subtarefa 2: Gerar relatórios.
  • Subtarefa 3: Executar simulações.
  • Etapa 2: Atribuir três subtarefas a três núcleos.
  • Etapa 3: Por fim, combine os resultados de cada subtarefa para obter o resultado final da tarefa original.
Example of 3 tasks running in parallel

Casos de uso do paralelismo

  • Cálculos científicos e simulações.
  • Processamento de dados.
  • Processamento de imagens.
  • Aprendizado de máquina.
  • Análise de risco.

Diferentes modelos de paralelismo

Semelhante à concorrência, o paralelismo também possui vários modelos diferentes para utilizar processadores multi-core de forma eficiente e recursos de computação distribuídos. Aqui estão alguns modelos-chave de paralelismo e seus usos:

1. Paralelismo de dados

Este modelo distribui os dados por vários processadores e executa a mesma operação em cada subconjunto de dados simultaneamente. É particularmente eficaz para tarefas que podem ser facilmente divididas em subtarefas independentes.

Exemplos:

  • OperaçõesSIMD (Single Instruction, Multiple Data, ou Instrução Única, Dados Múltiplos).
  • Processamento paralelo de matrizes.
  • Estrutura MapReduce.

Aplicações no mundo real:

  • Processamento de imagens e sinais
  • Análise de dados em grande escala
  • Simulações científicas

2. Paralelismo de tarefas

O paralelismo de tarefas envolve a divisão da tarefa geral em tarefas menores e independentes que podem ser executadas simultaneamente em diferentes processadores. Cada tarefa realiza uma operação diferente.

Exemplos:

  • Paralelismo baseado em threads em Java.
  • Tarefas paralelas em .NET.
  • Threads POSIX.

Aplicações no mundo real:

  • Servidores web que lidam com múltiplas solicitações de clientes.
  • Implementações de algoritmos paralelos.
  • Sistemas de processamento em tempo real.

3. Paralelismo em pipeline

No paralelismo em pipeline, as tarefas são divididas em etapas, e cada etapa é processada em paralelo. Os dados fluem através do pipeline, com cada etapa operando simultaneamente.

Exemplos:

  • Comandos de pipeline do Unix.
  • Pipelines de processamento de imagens.
  • Pipelines de processamento de dados em ferramentas ETL (Extract, Transform, Load).

Aplicações no mundo real:

  • Processamento de vídeo e áudio.
  • Aplicações de streaming de dados em tempo real.
  • Automação da linha de produção e montagem.

4. Modelo Fork/Join

Este modelo envolve dividir uma tarefa em subtarefas menores (fork), executá-las em paralelo e, em seguida, combinar os resultados (join). É útil para algoritmos de dividir para conquistar.

Exemplos:

  • Estrutura Fork/Join em Java.
  • Algoritmos recursivos paralelos (por exemplo, mergesort paralelo).
  • Intel Threading Building Blocks (TBB).

Aplicações no mundo real:

  • Tarefas computacionais complexas, como classificação de grandes conjuntos de dados.
  • Algoritmos recursivos.
  • Cálculos científicos em grande escala.

5. Paralelismo de GPU

O paralelismo de GPU aproveita os recursos de processamento massivamente paralelo das unidades de processamento gráfico (GPUs) para executar milhares de threads simultaneamente, tornando-o ideal para tarefas altamente paralelas.

Exemplos:

  • CUDA (Compute Unified Device Architecture) da NVIDIA.
  • OpenCL (Open Computing Language).
  • TensorFlow para aprendizado profundo.

Aplicações no mundo real:

  • Aprendizado de máquina e aprendizado profundo.
  • Renderização gráfica em tempo real.
  • Computação científica de alto desempenho.

Concorrência x paralelismo

Agora que você já tem um bom entendimento de como a concorrência e o paralelismo funcionam, vamos compará-los em vários aspectos para ver como podemos tirar o melhor proveito de ambos.

1. Utilização de recursos

  • Concorrência: executa várias tarefas em um único núcleo, compartilhando recursos entre as tarefas. Por exemplo, a CPU alterna entre tarefas durante períodos de inatividade ou espera.
  • Paralelismo: usa vários núcleos ou processadores para executar tarefas simultaneamente.

2. Foco

  • Concorrência: concentra-se em gerenciar várias tarefas ao mesmo tempo.
  • Paralelismo: concentra-se na execução de várias tarefas ao mesmo tempo.

3. Execução de tarefas

  • Concorrência: Tarefas executadas de maneira intercalada. A rápida troca de contexto da CPU cria uma ilusão de execução paralela.
  • Paralelismo: as tarefas são executadas de forma verdadeiramente paralela em diferentes processadores ou núcleos.

4. Troca de contexto

  • Concorrência: A troca frequente de contexto ocorre quando a CPU alterna entre tarefas para dar a aparência de execução simultânea. Às vezes, isso pode afetar negativamente o desempenho se as tarefas ficarem frequentemente ociosas.
  • Paralelismo: Troca de contexto mínima ou inexistente, uma vez que as tarefas são executadas em núcleos ou processadores separados.

5. Casos de uso

  • Concorrência: Tarefas vinculadas a E/S, como E/S de disco, comunicação de rede ou entrada do usuário.
  • Paralelismo: tarefas vinculadas à CPU que exigem processamento intensivo, como cálculos matemáticos, análise de dados e processamento de imagens.
A table explaining and comparing the differences between concurrency and parallelism

Podemos usar simultaneidade e paralelismo juntos?

Com base na comparação acima, podemos observar que a concorrência e o paralelismo se complementam em muitas situações. Mas antes de entrarmos em exemplos do mundo real, vamos ver como essa combinação funciona nos bastidores em um ambiente multi-core. Para isso, vamos considerar um servidor web que realiza leitura, gravação e análise de dados.

Etapa 1: Identificação de tarefas

Primeiro, você precisa identificar as tarefas vinculadas a E/S e as tarefas vinculadas à CPU em seu aplicativo. Neste caso:

  • Limitada por E/S – Leitura e gravação de dados.
  • Limitada pela CPU – Análise de dados.

Etapa 2: Execução simultânea

As tarefas de leitura e gravação de dados podem ser executadas em threads separadas dentro de um único núcleo, pois são tarefas vinculadas a E/S. O servidor usa um loop de eventos para gerenciar essas tarefas e alterna rapidamente entre threads, intercalando a execução da tarefa. Você pode usar uma biblioteca de programação assíncrona como Python asyncio para implementar esse comportamento de simultaneidade.

Etapa 3: Execução paralela

Vários núcleos podem ser atribuídos a tarefas limitadas pela CPU para lidar com elas em paralelo. Nesse caso, a análise de dados pode ser dividida em várias subtarefas e cada subtarefa será executada em um núcleo independente. Você pode usar uma estrutura de execução paralela como Python concurrent.futures para implementar esse comportamento.

Etapa 4: Sincronização e coordenação

Às vezes, threads em execução em núcleos diferentes podem depender uns dos outros. Em tais situações, mecanismos de sincronização como bloqueios e semáforos são necessários para garantir a integridade dos dados e evitar condições de corrida.

Visualization of concurrency and parallelism in multi-core processing

O trecho de código abaixo mostra como usar a concorrência e o paralelismo na mesma aplicação usando Python:

import asyncio
from concurrent.futures import ProcessPoolExecutor
import os

# Simular tarefa vinculada a E/S (leitura de dados)
async def read_data():
    await asyncio.sleep(1)  # Simular atraso de E/S
    data = [1, 2, 3, 4, 5]  # Dados fictícios
    print("Leitura de dados concluída")
    return data

# Simular tarefa vinculada a E/S (gravação de dados)
async def write_data(data):
    await asyncio.sleep(1)  # Simular atraso de E/S
    print(f"Gravação de dados concluída: {data}")

# Simular tarefa vinculada à CPU (análise de dados)
def analyze_data(data):
    print(f"Análise de dados iniciada na CPU: {os.getpid()}")
    result = [x ** 2 for x in data]  # Simular computação
    print(f"Análise de dados concluída na CPU: {os.getpid()}")
    return result

async def handle_request():
    # Concorrência: ler dados de forma assíncrona
    data = await read_data()
    
    # Paralelismo: analisar dados em paralelo
    loop = asyncio.get_event_loop()
    with ProcessPoolExecutor() as executor:
        analyzed_data = await loop.run_in_executor(executor, analyze_data, data)
    
    # Concorrência: gravar dados de forma assíncrona
    await write_data(analyzed_data)

async def main():
    # Simular o tratamento de várias solicitações
    await asyncio.gather(handle_request(), handle_request())

# Executar o servidor
asyncio.run(main())

Exemplos reais de combinação de concorrência e paralelismo

Agora, vamos discutir alguns casos de uso comuns em que podemos combinar concorrência e paralelismo para obter o desempenho ideal.

1. Processamento de dados financeiros

As principais tarefas de um sistema de processamento de dados financeiros incluem coleta, processamento e análise de dados, além de atender às operações diárias.

  • A concorrência é usada para buscar dados financeiros de vários recursos, como o mercado de ações, usando operações de E/S assíncronas.
  • Analisar os dados coletados para gerar relatórios. Essa é uma tarefa que exige muito da CPU, e o paralelismo é usado para executá-la em paralelo, sem afetar as operações diárias.

2. Processamento de vídeo

As principais tarefas de um sistema de processamento de vídeo incluem upload, codificação/decodificação e análise de arquivos de vídeo.

  • A concorrência pode ser usada para lidar com várias solicitações de upload de vídeo usando operações de E/S assíncronas. Isso permite que os usuários enviem vídeos sem esperar que outros uploads sejam concluídos.
  • O paralelismo é usado para tarefas que exigem muito da CPU, como codificação, decodificação e análise de arquivos de vídeo.

3. Extração de dados

As principais tarefas de um serviço de extração de dados incluem buscar dados de vários sites e realizar Parsing dos dados coletados para obter insights.

  • A obtenção de dados pode ser tratada usando a simultaneidade. Isso garante que a coleta de dados seja eficiente e não seja bloqueada enquanto aguarda respostas.
  • O paralelismo é usado para processar os dados coletados em vários núcleos da CPU. Ele melhora o processo de tomada de decisão da organização, fornecendo relatórios em tempo real.

Conclusão

Concorrência e paralelismo são dois conceitos-chave usados no desenvolvimento de software para melhorar o desempenho das aplicações. A concorrência permite que várias tarefas sejam executadas simultaneamente, enquanto o paralelismo acelera o processamento de dados usando vários núcleos de CPU. Embora tenham funcionalidades distintas, sua integração pode melhorar significativamente o desempenho de aplicações com tarefas vinculadas a E/S e CPU.

As ferramentas da Bright Data, como as APIs Web Scraper, as funções Web Scraper e o Navegador de scraping, foram projetadas para explorar totalmente essas técnicas e ajudá-lo com os desafios comuns do Scraping de dados da web. Elas usam operações assíncronas para coletar dados de várias fontes simultaneamente e processamento paralelo para analisar e organizar os dados rapidamente. Portanto, escolher um provedor de dados como a Bright Data, que já integrou simultaneidade e paralelismo em seu núcleo, pode economizar tempo e esforço, pois você não precisará implementar esses conceitos do zero durante o Scraping de dados.

Comece o teste grátis hoje mesmo!