Unidade de Concorrência
Uma unidade de concorrência é a abstração usada para representar as tarefas concorrentes de um programa e que serão escalonadas. Essa abstração costuma ser implementada como um tipo que pode ser instanciado pelo programador. Certas unidades não se parecem com um tipo, podendo ser uma função, mas por trás dos panos elas ainda são implementadas como um objeto.
Alguns exemplos que iremos explorar nos próximos capítulos:
-
Uma thread do SO é a unidade de concorrência mais primitiva de todas, escalonada pelo próprio sistema operacional.
Implementações:
threadingem Python;java.lang.Threadna JVM;System.Threading.Threadem .NET;std::threadem C++;<pthread.h>/pthread_te<threads.h>/thrd_tem C/C++; etc. -
Uma green thread (também chamada de user-space thread ou thread leve) é uma unidade de concorrência implementada e escalonada no user-space, apresentando uma semântica similar a de uma thread (memória compartilhada).
Uma green thread pode ser executada em uma de \( N \) threads do SO possíveis, e uma thread do SO pode executar uma de \( M \) green threads possíveis, definindo assim um mapeamento \( N:M \) entre ambas as unidades de concorrência.
Implementações: goroutines em Go; virtual threads em Java;
ThreadIdem Haskell;greenletem Python; etc. -
Um processo do SO é uma unidade de concorrência escalonada pelo próprio sistema operacional, com isolamento de memória entre dois ou mais processos. A comunicação entre processos é feita usando técnicas de Inter-Process Communication (IPC), como signals, pipes e filas.
Implementações:
multiprocessingem Python,std::processeipc-channelem Rust;<unistd.h>e<spawn.h>em C/C++; Boost.Process e Boost.Interprocess em C++;java.lang.Processna JVM;oseos/execem Go;System.Diagnostics.ProcesseSystem.IO.Pipesem .NET;child_processeclusterem Node.js;pcntl_*,posix_*,proc_*emsg_*em PHP;Foundation.ProcesseFoundation.Pipeem Swift; etc. -
Um green process (também chamado de user-space process, ator ou actor)1 é uma unidade de concorrência implementada e escalonada no user-space, apresentando uma semântica similar a de um processo do SO (memória isolada).
Implementações: processos da BEAM,
lunatic::processem WebAssembly,dart:isolateem Dart, Cloudflare Workers, etc. -
Uma corotina é uma unidade de concorrência implementada e escalonada no user-space, e sempre escalonada de maneira cooperativa (daí o nome).
Uma corotina é uma generalização de uma rotina/função: enquanto uma rotina/função tradicional tem um ponto de entrada e um ponto de saída, uma corotina pode ter vários. Como são escalonadas cooperativamente, o próprio programador decide quando suspender uma corotina, usando uma função ou keyword geralmente chamada
yield/await.Podemos dividir corotinas entre stackful e stackless.
-
Uma corotina stackful é uma corotina que possui uma pilha de chamadas (call stack) completa. O runtime aloca memória (entre 4 KB-1MB) para construir a pilha, e gerencia ela ao longo da vida da corotina. Isso permite que a corotina seja suspensa em qualquer profundidade na cadeia de chamadas de funções, e não apenas a partir da função mais externa. Uma corotina stackful é equivalente a uma green thread cooperativa.
Implementações:
coroutine.*em Lua (a implementação mais famosa), goroutines em Go (quando cooperativas), virtual threads em Java,<ucontext.h>em C/C++. -
Uma corotina stackless é uma corotina que não carrega sua própria pilha de chamadas. Invés disso, o compilador transforma a corotina num struct, que funciona como uma máquina de estados. Cada chamada de
yield/awaité transformada numa transição de estado.Apenas as variáveis locais que persistem entre pontos de suspensão (isto é, que são usadas em ambos os lados de um
yield/await) são salvas em memória, mais especificamente num pequeno frame do heap (entre 512 e 1024 bytes).Por não possuir uma pilha de chamadas, uma corotina stackless só pode ser suspensa no nível mais alto do seu próprio corpo, nunca de dentro de uma chamada de função aninhada. Para mais detalhes de como corotinas stackless funcionam por baixo dos panos, veja Em Detalhe: Corotinas.
Corotinas stackless são a principal forma de implementar a sintaxe
async/awaitno modelo de programação assíncrono.Implementações:
asyncio/coroutineem Python, corotinas em C++20,async/awaitem várias linguagens (C#, Rust, JavaScript, Dart, Swift, Python, Zig), generators em várias linguagens (Python, JavaScript, PHP),AmpeReactPHPem PHP.
-
-
Uma fiber é uma unidade de concorrência, geralmente equivalente a uma corotina stackful.
Implementações:
Swoole\Coroutinee\Fiberem PHP,Fiberem Ruby,Boost.Fiberem C++, funções de fibers (ConvertThreadToFiber,CreateFiber,SwitchToFiber, etc) expostas na API Win32 em C/C++ (<windows.h>),node-fibersem Node.js, etc.
-
O nome “Ator” deriva do modelo de concorrência chamado “modelo de Ator”, que é muito associado com green processes, em especial por causa da implementação mais famosa desse, presente na BEAM. No entanto, nem todas implementações do modelo de Ator implementam Atores com green processes. Dois exemplos disso são Ractors (implementação da linguagem Ruby) e Akka/Pekko (uma biblioteca e runtime que implementa o modelo de Ator na JVM), pois os Atores de ambas as implementações não tem um isolamento de memória no heap, nem possuem um garbage-collector por ator. O isolamento é feito por convenção ou por restrição da API pública, mas ele não existe a nível de memória. ↩