DEV Community

Dev Maiqui 🇧🇷
Dev Maiqui 🇧🇷

Posted on • Edited on

💻 Olá mundo da programação concorrente

Nest post vamos dar uma introdução sobre a programação concorrente. Vamos abordar também um pouco sobre a diferença entre concorrência e paralelismo, assuntos que podem parecer sinônimos, mas quando falamos em programação de computadores há diferenças.

Neste post foram abordados os assuntos principais de cada pesquisa feita. Para mais detalhes sugiro a leitura ou visualização delas:

  1. Site da Intel;
  2. Livro C++ Concurrency in Action;
  3. Livro Erlang and OTP in Action;
  4. Vídeo O que são os NÚCLEOS, THREADS, DUO, QUAD?.
  5. Artigo Concurrency vs Parallelism
  6. Artigo Threads: O que são e para que servem em um processador?

A Jornada do Autodidata em Inglês

O que é concorrência?

Conforme o livro C++ Concurrency in Action a concorrência é sobre duas, ou mais atividades separadas/independentes que acontecem ao mesmo tempo. Encontramos a concorrência como parte natural da vida; podemos caminhar e falar ao mesmo tempo, ou realizar ações diferentes com cada mão, e cada um pode viver a sua vida independentemente do outro - você pode ver futebol enquanto eu vou nadar, e assim por diante.

Image description


Concorrência em sistemas de computador

Quando falamos de concorrência em computadores, nos referimos a um único sistema que executa múltiplas atividades independentes ao mesmo tempo e sem ordem específica, em vez de sequencialmente (ordem específica).

Sequencialmente (ordem específica):

Image description

Em um jogo de cartas, os jogadores jogam ao mesmo tempo, mas o jogo começa com o baralho sendo embaralhado, as cartas sendo entregues aos jogadores e, depois, cada jogador joga na sua vez, há uma ordem especifica:
Image description

Ao mesmo tempo e sem ordem específica:

Image description

Definição do livro Erlang and OTP in Action:

Concorrência é apenas mais uma palavra para paralelo? Quase, mas não exatamente, pelo menos quando estamos falando de computadores e programação.

Uma definição semiformal popular diz algo como: "Aquelas coisas que não têm nada que as obrigue a acontecer em uma ordem específica são ditas como concorrentes". Por exemplo, dada a tarefa de ordenar dois baralhos de cartas, você poderia ordenar um baralho primeiro e depois o outro; ou se você tivesse braços e olhos extras, você poderia ordenar ambos em paralelo. Nada exige que você os faça em uma determinada ordem; portanto, são tarefas concorrentes. Elas podem ser feitas em qualquer ordem, ou você pode pular para frente e para trás entre as tarefas até que ambas sejam feitas; ou, se você tiver os recursos extras (ou talvez alguém para ajudá-lo), você pode executá-las simultaneamente de forma verdadeiramente paralela.

Image description

Isto pode soar estranho: não deveríamos dizer que as tarefas só são concorrentes se elas estão acontecendo ao mesmo tempo? Bem, a questão com essa definição é que elas podem acontecer ao mesmo tempo, e nós somos livres para agendá-las de acordo com nossa conveniência. Tarefas que precisam ser feitas simultaneamente não são tarefas separadas, enquanto algumas tarefas são separadas mas não concorrentes e devem ser feitas em ordem, como quebrar o ovo antes de fazer a omelete. As demais são concorrentes.

Núcleos vs. Threads

Conforme o site da Intel, um thread, ou thread de execução, é um termo de software para a sequência básica ordenada de instruções que pode ser passada ou processada por um único núcleo/core de CPU.

  • Single thread: Cada core corresponde à um thread;
  • Multi thread (Hyper-threading): Cada core possui mais de um thread.

Image description

Créditos da imagem acima: https://www.mobilebit.com.br/tecnologia/2020/12/15/threads-o-que-sao-para-que-servem-em-um-processador/

Hyper-threading (threads adicionais)

Image description

  • As Threads adicionais são conhecidas popularmente como núcleos lógicos ou núcleos virtuais.
  • As Threads adicionais não tem o mesmo poder de processamento de um núcleo físico (thread real).
  • As Threads adicionais vão ajudar em softwares que não conseguem lidar com vários núcleos físicos.

Em um processador de quatro núcleos e oito threads, como é o caso do processador Intel Core i7-1165G7 você verá em um sistema Linux no Monitor do Sistema oito gráficos representando os núcleos, ou seja, oito linhas de execução (threads) - quatro núcleos físicos e quatro núcleos virtuais.

Image description

Veja no site as especificações do processador Intel Core i7-1165G7
Image description

Formação TS

Alternância de Tarefas (Task Switching / Context Switching)

Executando mais de uma tarefa ao mesmo tempo, mas não de forma paralela.

Image description

Alternância de tarefas em uma máquina com um só núcleo e um só thread:
Image description

Historicamente, a maioria dos computadores de mesa (desktop) tiveram um processador, com uma única unidade de processamento ou núcleo (single core processor). Tal máquina só pode executar uma tarefa de cada vez, mas pode alternar entre tarefas muitas vezes por segundo. Ao fazer um pouco de uma tarefa e depois um pouco de outra e assim por diante, parece que as tarefas acontecem simultaneamente. Isto chama-se alternância de tarefas (task switching).

Exemplos de processadores single core:

Execução paralela

Executando mais de uma tarefa ao mesmo tempo e de forma paralela (lado a lado).

Image description

Créditos da imagem acima: https://jenkov.com/.

Computadores contendo múltiplos processadores têm sido utilizados para servidores e tarefas de computação de alto desempenho durante anos, e computadores baseados em processadores com mais do que um núcleo num único chip (multicore processors) tornam-se cada vez mais comuns em máquinas ‘desktop’. Quer tenham processadores múltiplos ou núcleos múltiplos dentro de um processador (ou ambos), estes computadores conseguem executar genuinamente mais do que uma tarefa em paralelo.

Exemplos de processadores multi core:

Alternância de Tarefas vs. Execução Paralela

Alternância de Tarefas vs. Execução Paralela

Créditos da imagem acima: Livro C++ Concurrency in Action.

GIF Concurrency vs Parallelism

A Figura 1.1 mostra um cenário idealizado de um computador com precisamente duas tarefas a fazer, cada uma dividida em 10 blocos de igual tamanho. Numa máquina de núcleo duplo (com dois núcleos de processamento), cada tarefa pode executar no seu próprio núcleo. Numa máquina de núcleo único que faz a troca de tarefas (task switching), os blocos de cada tarefa são intercalados. Mas também são espaçados um pouco (na figura 1.1, isto é mostrado pelas barras cinzentas que separam os blocos sendo mais espessas do que as barras separadoras mostradas para a máquina de núcleo duplo); para fazer a intercalação, o sistema tem de executar uma troca de contexto cada vez que muda de uma tarefa para outra, e isto leva tempo. Para executar uma mudança de contexto, o sistema operacional tem de guardar o estado da CPU e o ponteiro de instruções para a tarefa atualmente em execução, determinar para qual tarefa mudar, e recarregar o estado da CPU para a tarefa para a qual é mudada. A CPU terá então potencialmente de carregar a memória para as instruções e os dados para a nova tarefa na memória transitória (cache), o que pode impedir a CPU de executar quaisquer instruções, causando mais atrasos.

Execução paralela e concorrente

Executando mais de uma tarefa ao mesmo tempo mas não de forma paralela em cada CPU e as duas CPUs trabalhando de forma paralela.

Em uma CPU acontece a alternância de tarefas, ou seja, uma tarefa deve parar para a outra prosseguir. As duas CPUs estão trabalhando em paralelo (lado a lado), ou seja, uma CPU não precisar parar para a outra prosseguir.

Image description

Créditos da imagem acima: Livro C++ Concurrency in Action.

Embora a disponibilidade de concorrência no hardware seja mais óbvia com sistemas multiprocessadores ou multinúcleo, alguns processadores podem executar vários fios (threads) num único núcleo (Hyper-threading). O fator importante a considerar é o número de fios do hardware, sendo a medida de quantas tarefas independentes o hardware pode genuinamente executar concorrentemente. Mesmo com um sistema com uma concorrência genuína de hardware, é fácil ter mais tarefas do que o hardware pode executar em paralelo, por isso a alternância de tarefas continua a ser utilizada nestes casos. Por exemplo, num computador ‘desktop’ típico pode haver centenas de tarefas em execução, executando operações em segundo plano (background), mesmo quando o computador está nominalmente inativo. É a alternância de tarefas que permite executar estas tarefas em segundo plano e executar o processador de texto, compilador, editor, e browser da web (ou qualquer combinação de aplicações) tudo de uma só vez. A Figura 1.2 mostra a alternância de tarefas entre quatro tarefas numa máquina dual-core, mais uma vez para um cenário idealizado com as tarefas divididas ordenadamente em blocos de igual tamanho.

Podemos também ter uma divisão de uma única tarefa em subtarefas que podem ser executadas concorrentemente e em paralelo.

Image description

Créditos da imagem acima: https://jenkov.com/.

Conforme o livro C++ Concurrency in Action a concorrência e o paralelismo têm significados amplamente sobrepostos no que diz respeito ao código multithreaded. De fato, para muitos eles significam a mesma coisa. A diferença é principalmente uma questão de nuance, foco e intenção. Ambos os termos são sobre executar múltiplas tarefas simultaneamente, usando o hardware disponível, mas o paralelismo é muito mais orientado para o desempenho. As pessoas falam em paralelismo quando sua principal preocupação é aproveitar o hardware disponível para aumentar o desempenho do processamento de dados em massa, enquanto as pessoas falam em concorrência quando sua principal preocupação é a separação de preocupações, ou capacidade de resposta.

Assíncrono X Síncrono

Podemos encontrar a palavra async (asynchronous = assíncrono) como uma palavra reservada em linguagens de programação para converter código sequencial em código concorrente, como na linguagem Elixir com o Task.async.

Tanto o código Síncrono como Assíncrono são executados aos mesmo tempo, a diferença é que o código assíncrono não há uma ordem específica e por isso é considerado concorrente:

Assíncrono = ao mesmo tempo = velocidades diferentes = sem ordem específica = concorrente:

Image description

Síncrono = ao mesmo tempo = velocidades iguais = ordem específica = não concorrente:

Image description

Image description


Concorrência com múltiplos processos

Image description

Créditos da imagem acima: Livro C++ Concurrency in Action.

A primeira maneira de fazer uso da concorrência dentro de uma aplicação é dividir a aplicação em múltiplos processos separados, em uma única thread, que são executados ao mesmo tempo, da mesma forma que você pode executar seu navegador web e processador de texto ao mesmo tempo. Estes processos separados podem então passar mensagens uns para os outros através de todos os canais normais de comunicação interprocessados (signals, sockets, files, pipes, etc.), como mostrado na figura 1.3. Uma desvantagem é que tal comunicação entre processos é muitas vezes complicada de configurar ou lenta, ou ambos, porque os sistemas operacionais normalmente fornecem muita proteção entre processos para evitar que um processo modifique acidentalmente os dados pertencentes a outro processo. Outra desvantagem é que há uma sobrecarga inerente na execução de múltiplos processos: leva tempo para iniciar um processo, a operação deve dedicar recursos internos ao gerenciamento do processo, e assim por diante.

Nem tudo é negativo: a proteção adicional que os sistemas operacionais normalmente proporcionam entre os processos e os mecanismos de comunicação de nível superior significa que pode ser mais fácil escrever código concorrente seguro com os processos do que com as threads. De fato, ambientes como o fornecido pela linguagem de programação Erlang (www.erlang.org/) utilizam processos como o bloco fundamental da concorrência com grande efeito.

A utilização de processos separados para a concorrência também tem uma vantagem adicional - você pode executar os processos separados em máquinas distintas conectadas através de uma rede. Embora isto aumente o custo de comunicação, em um sistema cuidadosamente projetado pode ser uma forma econômica de aumentar o paralelismo disponível e melhorar o desempenho.

Image description

Créditos da imagem acima: Livro Erlang and OTP in Action.

Concorrência com múltiplos threads

Image description

Créditos da imagem acima: Livro C++ Concurrency in Action.

A abordagem alternativa para a concorrência é executar vários threads em um único processo. Os threads são muito parecidos com processos leves: cada thread funciona independentemente dos outros, e cada um pode executar uma seqüência diferente de instruções. Mas todos os threads em um processo compartilham o mesmo espaço de endereço, e a maioria dos dados pode ser acessada diretamente de todos os threads - variáveis globais permanecem globais, e ponteiros ou referências a objetos ou dados podem ser passados entre os threads. Embora muitas vezes seja possível compartilhar memória entre processos, isto é complicado de configurar e muitas vezes difícil de gerenciar, porque os endereços de memória dos mesmos dados não são necessariamente os mesmos em processos diferentes. A Figura 1.4 mostra dois threads dentro de um processo comunicando através da memória compartilhada.

O espaço de endereços compartilhado e a falta de proteção dos dados entre os threads fazem com que a sobrecarga (overhead) associada ao uso de múltiplos threads seja muito menor do que a do uso de múltiplos processos, porque o sistema operacional tem menos contabilidade (bookkeeping) a fazer. Mas a flexibilidade da memória compartilhada também vem com um preço: se os dados são acessados por múltiplos threads, o programador da aplicação deve garantir que a visualização dos dados vistos por cada thread seja consistente sempre que ela for acessada. As questões relacionadas ao compartilhamento de dados entre threads, e as ferramentas a serem usadas e as diretrizes a serem seguidas para evitar problemas são abordadas no livro C++ Concurrency in Action. Os problemas não são intransponíveis, desde que se tome o cuidado adequado ao escrever o código, mas eles significam que muita reflexão deve ir para a comunicação entre os threads.

A baixa sobrecarga associada ao lançamento e à comunicação entre múltiplos threads dentro de um processo em comparação com o lançamento e a comunicação entre múltiplos processos de uma única thread significa que esta é a abordagem favorecida para a concorrência nas linguagens orientadas a objetos, incluindo C++, apesar dos problemas potenciais decorrentes da memória compartilhada.


Conclusão

Se eu pudesse resumir concorrência em poucas palavras, eu diria que concorrencia é quando duas ou mais atividades independentes acontecem ao mesmo tempo quando não há uma ordem específica. Esse post foi realmente só uma introdução sobre o assunto. Há muito conhecimento ainda pra ser estudado quando falamos em concorrência com múltiplos threads ou concorrência com múltiplos processos. Cada abordagem tem seus pontos positivos e negativos e, dependendo da linguagem de programação que você atua; você terá que focar mais em umas dessas abordagens. O próximo passo agora é procurar colocar a mão no código visando esses conceitos e como isso pode melhorar e trazer benefícios ao seu software.

Formação TS

Top comments (10)

Collapse
 
postelxpro profile image
Elxpro

Eu vou ler.... vou ter que querer ler haha um grande abraco meu amigo

Collapse
 
maiquitome profile image
Dev Maiqui 🇧🇷

Claro!! tem que ler né heheh e já me dá umas dicas depois de melhoria heheh abração tmj :)

Collapse
 
elixir_utfpr profile image
Elixir UTFPR (por Adolfo Neto)

Oi, recomendo também Programação Concorrente de Luciano Ramalho: youtube.com/watch?v=FYKNHk3Ze8A

Collapse
 
maiquitome profile image
Dev Maiqui 🇧🇷

boa!!!

Collapse
 
rodrigoazv profile image
Rodrigo Azevedo

Show Maiqui!

Collapse
 
maiquitome profile image
Dev Maiqui 🇧🇷

Obrigado pelo feedback Rodrigo :)

Collapse
 
adrielbento profile image
Adriel Bento

Parabéns pelo post @maiquitome

Collapse
 
maiquitome profile image
Dev Maiqui 🇧🇷

Brigadão pelo feedback Adriel tmj :)

Collapse
 
helderalvescouto profile image
Helder Alves Couto

Muito bom Maiqui! 👏🏻👏🏻👏🏻

Collapse
 
maiquitome profile image
Dev Maiqui 🇧🇷

Brigadão pelo feedback Helder :)