💻 Olá mundo da programação concorrente
Tags: braziliandevs, concurrency, programming, performance
Published: 2022-06-24 20:44:36

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?](https://bit.ly/3Qk9vIJ).
5. Artigo [Concurrency vs Parallelism](https://bit.ly/3zYBEiG)
6. Artigo [Threads: O que são e para que servem em um processador?](https://bit.ly/3nbJ2zF)

--------------------------------------------------------

* [O que é concorrência?](#o-que-e)
* [Concorrência em sistemas de computador](#concorrencia)
 - [Núcleos vs. Threads](#cores-threads)
 - [Hyper-threading (threads adicionais)](#hyper-threading)
 - [Alternância de Tarefas (Task Switching)](#task-switching)
 - [Execução Paralela](#execucao-paralela)
 - [Alternância de Tarefas vs. Execução Paralela](#alternancia-execucao-paralela)
 - [Execução paralela e concorrente](#execucao-paralela-concorrente)
 - [Assíncrono X Síncrono](#async)
* Abordagens à concorrência
 - [Concorrência com múltiplos processos](#multiplos)
 - [Concorrência com múltiplos threads](#multiplos-threads)
* [Conclusão](#conclusao)

[![A Jornada do Autodidata em Inglês](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkoqkmh9hic17tnddjf6.png)](https://go.hotmart.com/R93865620U)


# O que é concorrência? <a name="o-que-e">

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sol2k2xy3tbamt5vfyp1.gif) 

**********************************************************

# Concorrência em sistemas de computador <a name="concorrencia">


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lq0llmcctbzsxl9zaes.gif)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1z92ds8aed69se0ns0ix.gif) 

_*Ao mesmo tempo e sem ordem específica:*_

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/573snicm4hdibo376fbn.gif) 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kogmcmwnpa1uzg733grh.gif)

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 <a name="cores-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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/en5hgk383wayz2oah0rj.png) 

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) <a name="hyper-threading">

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5to4bl1i8wape4sum1ls.png)

* 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](https://intel.ly/3mUAb5j) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwzv4ej9jiuhrqfooc50.png) 

Veja no site as especificações do processador [Intel Core i7-1165G7](https://intel.ly/3mUAb5j)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zu7oglymdtfqfdxfbch2.png) 


[![Formação TS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isxopdurvofcx18iptvb.png)](https://go.hotmart.com/H93763859E)


### Alternância de Tarefas (Task Switching / Context Switching) <a name="task-switching">

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cm0bmjudhpfrtbdk5ees.gif)

__Alternância de tarefas__ em uma máquina com __um só núcleo__ e __um só thread__:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/meus5e59m0urxucgxnqf.png) 

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:

* [Intel Celeron G440](https://intel.ly/3xDDyUp)
 - Frequência baseada em processador: 1.60 GHz 
 - Nº de threads: 1
* [Intel Celeron G470](https://intel.ly/3zu0J4Y) 
 - Frequência baseada em processador: 2.00 GHz 
 - Nº de threads: 2


### Execução paralela <a name="execucao-paralela">

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cr9fe8bhehftlje6y4y.png)

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:

* [Intel Core i7-12650HX (12ª Geração)](https://intel.ly/3zy0k1j)
 - Nº de núcleos: 14
 - Frequência turbo max: 4.70 GHz
 - Nº de thrFrequência baseada em processadoreads 20
* [Intel Core i9-12900HX (12ª Geração)](https://intel.ly/3trgFB6)
 - Nº de núcleos: 16
 - Frequência turbo max: 5.00 GHz
 - Nº de threads 24
* [Intel Xeon Platinum 8362 (3ª Geração)](https://intel.ly/3xH0lP4)
 - Nº de núcleos: 32
 - Frequência turbo max: 3.60 GHz
 - Frequência baseada em processador: 2.80 GHz
 - Nº de threads: 64
* [Intel Xeon Platinum 8380 (3ª Geração)](https://intel.ly/3zvTwkK)
 - Nº de núcleos: 40
 - Frequência turbo max: 3.40 GHz
 - Frequência baseada em processador: 2.30 GHz
 - Nº de threads: 80
 
### Alternância de Tarefas vs. Execução Paralela <a name="alternancia-execucao-paralela">

![Alternância de Tarefas vs. Execução Paralela](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzhm1ctgmuiyefdzox6s.png) 

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

![GIF Concurrency vs Parallelism](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ukmgjtfd3r8mo7uwi93.gif)


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 <a name="execucao-paralela-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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xc6bmncecyp19n5xt8y.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/057kjjd08wmftrhd50ic.png)

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 <a name="async">

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](https://hexdocs.pm/elixir/1.13/Task.html).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx3ifhmuoc2vnse9kwf0.gif)
 


__Síncrono__ = ao mesmo tempo = velocidades iguais = ordem específica = não concorrente:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt7561sg72p1itlu8rqo.gif)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jy0ecxof6fus6u35v2si.gif)


**********************************************************

### Concorrência com múltiplos processos <a name="multiplos">

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2kyp7ssbwlv0pzvrqc2.png) 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzmhbnw1omkdjb4ez0zh.png)

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

### Concorrência com múltiplos threads <a name="multiplos-threads">

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/250ez3jjz60ck78f39aj.png)

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 <a name="conclusao">

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72gnny6fjwnmku1x3rr4.png)](https://go.hotmart.com/H93763859E)