đ» OlĂĄ mundo da programação concorrente
Dev Maiqui đ§đ·
Posted on June 24, 2022
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:
- Site da Intel;
- Livro C++ Concurrency in Action;
- Livro Erlang and OTP in Action;
- VĂdeo O que sĂŁo os NĂCLEOS, THREADS, DUO, QUAD?.
- Artigo Concurrency vs Parallelism
- Artigo Threads: O que sĂŁo e para que servem em um processador?
- O que Ă© concorrĂȘncia?
- ConcorrĂȘncia em sistemas de computador
- Abordagens Ă concorrĂȘncia
- ConclusĂŁo
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.
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):
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:
Ao mesmo tempo e sem ordem especĂfica:
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.
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.
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)
- 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.
Veja no site as especificaçÔes do processador Intel Core i7-1165G7
AlternĂąncia de Tarefas (Task Switching / Context Switching)
Executando mais de uma tarefa ao mesmo tempo, mas nĂŁo de forma paralela.
AlternĂąncia de tarefas em uma mĂĄquina com um sĂł nĂșcleo e um sĂł thread:
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
- FrequĂȘncia baseada em processador: 1.60 GHz
- NÂș de threads: 1
-
Intel Celeron G470
- FrequĂȘncia baseada em processador: 2.00 GHz
- NÂș de threads: 2
Execução paralela
Executando mais de uma tarefa ao mesmo tempo e de forma paralela (lado a lado).
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)
- 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)
- NÂș de nĂșcleos: 16
- FrequĂȘncia turbo max: 5.00 GHz
- NÂș de threads 24
-
Intel Xeon Platinum 8362 (3ÂȘ Geração)
- 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)
- 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
Créditos da imagem acima: Livro C++ Concurrency in Action.
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.
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.
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:
SĂncrono = ao mesmo tempo = velocidades iguais = ordem especĂfica = nĂŁo concorrente:
ConcorrĂȘncia com mĂșltiplos processos
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.
Créditos da imagem acima: Livro Erlang and OTP in Action.
ConcorrĂȘncia com mĂșltiplos threads
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.
Posted on June 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.