Threads, concorrência e paralelismo

Thread é uma continuidade dentro do processo
Uma thread é um fluxo de controle dentro de um processo: possui um ponto atual de execução, registradores, pilha e estado que permitem avançar instruções. O processo reúne memória, recursos e proteção; suas threads usam essa moldura para realizar trabalho.
Todo processo em execução possui ao menos uma thread em modelos como POSIX e Windows. Um processo multithread mantém mais de uma continuidade. Enquanto uma thread espera dados da rede, outra pode interpretar uma página e uma terceira pode responder a uma solicitação já pronta — desde que o programa tenha sido estruturado para isso.
Na aula anterior, acompanhamos programa, processo, estados e troca de contexto. Agora abriremos a estrutura do processo. A pergunta deixa de ser apenas “qual processo está ativo?” e passa a incluir “quais fluxos podem progredir, quais recursos compartilham e em que momento executam?”.
Threads compartilham recursos e preservam contexto
Threads do mesmo processo normalmente compartilham:
- código e bibliotecas carregadas;
- dados globais e heap;
- espaço de endereçamento virtual;
- descritores de arquivos, pipes e sockets;
- diretório atual, ambiente e várias credenciais do processo;
- limites e outros atributos definidos pela plataforma.
Cada thread precisa manter separadamente o que define sua continuidade:
- identificador de thread;
- registradores e contador de instrução;
- pilha de chamadas e variáveis automáticas nela armazenadas;
- estado de escalonamento e, conforme a plataforma, prioridade;
- dados locais da thread, máscara de sinais ou estruturas equivalentes.
Esse arranjo torna a comunicação eficiente: uma thread pode produzir um objeto no heap e outra pode consumi-lo sem copiar todo o processo. A mesma proximidade amplia o impacto de erros. Escrita fora dos limites, fechamento indevido de um descritor ou alteração sem coordenação pode afetar todas as threads e derrubar o processo inteiro.
Isolamento entre processos costuma ser mais forte porque cada um recebe espaço virtual e recursos próprios. Threads são mais acopladas. A escolha entre processos e threads não é apenas “pesado versus leve”: envolve isolamento de falhas, segurança, comunicação, implantação e suporte do runtime.
Thread do sistema não é qualquer tarefa do programa
Bibliotecas e linguagens oferecem abstrações como tarefas, futures, promises, corrotinas e goroutines. Elas expressam trabalho ou continuidade, mas não precisam corresponder uma a uma a threads do sistema operacional.
Um runtime pode multiplexar milhares de tarefas sobre um conjunto menor de threads, mover continuações entre threads ou executar várias etapas na mesma thread. Em outra plataforma, uma tarefa de CPU pode ser enviada a um pool. Portanto, “criei uma task” não prova “o sistema criou uma thread”.
Também existem threads gerenciadas diretamente por bibliotecas de sistema, como Pthreads, e threads nativas do Windows. Mesmo nesses casos, a API define atributos, criação, término e sincronização; o código não administra registradores físicos por conta própria.
Criar trabalho exige definir como ele termina
Em POSIX, pthread_create() inicia uma thread dentro do processo com uma função de entrada e atributos. A thread termina ao retornar dessa função, chamar pthread_exit() ou seguir outro mecanismo previsto. Outra thread pode usar pthread_join() para esperar seu término e recolher o valor associado.
Uma thread joinable conserva informações necessárias até que alguém faça o join. Uma thread detached libera esses recursos automaticamente ao terminar e não pode ser coletada da mesma forma. Isso é decisão de ciclo de vida, não indicação de que ela executa em segundo plano ou é menos importante.
No Windows, funções e runtimes oferecem contratos próprios para criar, aguardar e encerrar threads. Forçar término abrupto é perigoso: a thread pode manter locks, memória em estado intermediário ou recursos que não serão finalizados corretamente. Cancelamento robusto costuma ser cooperativo — o trabalho recebe um sinal, alcança um ponto seguro, desfaz ou conclui efeitos e então termina.
Criar uma thread para cada item também não é regra de desempenho. Cada uma consome pilha, estruturas do sistema e tempo de agendamento. Pools reutilizam um conjunto controlado de trabalhadores e aplicam fila, mas ainda precisam de limites, cancelamento e tratamento de sobrecarga.
Sequencial, concorrente e paralelo respondem a perguntas diferentes
Uma execução sequencial conclui uma etapa antes de iniciar a próxima no fluxo observado. É simples e pode ser a melhor escolha quando o trabalho é pequeno ou estritamente dependente.
Concorrência é a composição de trabalhos cujos períodos de vida se sobrepõem e que podem progredir independentemente. Em uma única CPU, o sistema pode intercalar pequenos intervalos: executa A, muda para B, atende C e retorna a A. Há progresso concorrente sem duas instruções dessas threads serem executadas no mesmo instante físico.
Paralelismo é a execução simultânea de mais de um trabalho, usando múltiplos processadores, núcleos ou unidades capazes de operar ao mesmo tempo. Um programa concorrente pode explorar paralelismo quando existem recursos e partes independentes. Ele também pode continuar concorrente em apenas uma CPU.
Considere três operações da plataforma de estudos:
- A busca o conteúdo no armazenamento;
- B transforma uma imagem de capa;
- C atualiza um índice de pesquisa.
Se B depende do conteúdo obtido por A, essa parte continua ordenada. C talvez possa progredir sobre outro item. Executar tudo sequencialmente desperdiça oportunidades; tentar paralelizar etapas dependentes produz erro ou espera. O desenho correto começa pelas dependências, não pela quantidade de threads.
Interleaving torna a ordem observada variável
Em execução concorrente, operações de diferentes threads podem se entrelaçar. A sequência global pode ser A1, B1, A2, C1, embora cada thread preserve sua própria ordem definida. Uma execução seguinte pode produzir outra combinação porque interrupções, eventos, carga e decisões do sistema mudam.
Isso não significa que qualquer ordem seja permitida. A linguagem, a biblioteca e o hardware definem regras de memória; o programa adiciona sincronização para preservar invariantes. Mas confiar na ordem que “sempre aconteceu nos testes” é frágil quando não existe contrato que a garanta.
Uma variável compartilhada ilustra o problema. Se duas threads leem o mesmo valor, calculam atualizações e gravam sem coordenação, uma escrita pode sobrescrever a outra. O resultado não é consertado apenas porque existem dois núcleos — o paralelismo pode tornar a sobreposição mais provável. A aula sobre condições de corrida será canônica para esses mecanismos.
O conteúdo de concorrência, isolamento e MVCC em bancos de dados enfrenta outra camada: transações simultâneas e estados persistentes. Um SGBD pode usar threads internamente, mas mutex de aplicação e isolamento transacional não são soluções intercambiáveis.
Assincronia descreve início, espera e continuação
Uma operação é assíncrona quando sua conclusão é separada da chamada inicial e o programa pode continuar fazendo algo útil, devolver controle ou registrar uma continuação em vez de bloquear até o resultado chegar.
Assincronia não garante paralelismo. Uma única thread pode dirigir um loop de eventos: inicia várias operações de rede, registra interesses e executa as continuações conforme eventos ficam prontos. Durante a espera externa, não existe necessidade de manter uma thread dedicada consumindo uma CPU.
Também é possível implementar uma interface assíncrona transferindo trabalho bloqueante para outra thread. Por isso, observar async, await, promise ou future não revela sozinho o mecanismo. A documentação da API e do runtime precisa dizer se a operação usa I/O assíncrona, pool, thread dedicada ou conclusão imediata.
No modelo async/await do C#, por exemplo, await pode ceder o controle até uma tarefa terminar sem bloquear a thread durante I/O. Trabalho intensivo de CPU usa outra estratégia quando precisa preservar responsividade ou obter paralelismo. Essa é uma implementação concreta, não uma regra de sintaxe para todas as linguagens.
CPU-bound e I/O-bound pedem decisões diferentes
Um trabalho CPU-bound passa a maior parte do tempo calculando. Redimensionar milhares de imagens, comprimir dados ou calcular hashes pode aproveitar múltiplos núcleos se os itens forem independentes e a sobrecarga não superar o ganho.
Um trabalho I/O-bound passa grande parte do tempo aguardando armazenamento, rede, banco ou outro serviço. Aumentar threads pode ocultar algumas esperas, mas uma interface assíncrona pode atender muitas operações pendentes com menos threads. O limite real talvez esteja no dispositivo, na conexão ou no serviço externo.
Classificar pela aparência é insuficiente. Uma requisição “de banco” pode executar consulta cara no servidor; uma tarefa “de arquivo” pode incluir compressão intensa. Meça tempo de CPU, espera, vazão, latência e uso do recurso limitante.
Mais threads não significam mais velocidade
Paralelizar possui limites:
- a parte estritamente sequencial continua limitando o tempo total;
- criar, trocar e coordenar threads adiciona trabalho;
- disputa por locks transforma paralelismo em espera;
- caches e largura de banda de memória são compartilhados;
- muitas threads prontas aumentam filas e trocas de contexto;
- tarefas pequenas podem custar menos quando executadas diretamente;
- afinidade, NUMA e limites de contêiner alteram CPUs realmente disponíveis.
Uma máquina anunciar muitos processadores lógicos não garante que o processo possa usar todos. Políticas, cotas, outros programas e topologia importam. A próxima aula explicará como escalonadores escolhem trabalho pronto e quais métricas ajudam a avaliar a política.
O objetivo também pode não ser reduzir o tempo total. Concorrência pode melhorar responsividade: a interface continua atendendo eventos enquanto um cálculo ocorre. Assincronia pode melhorar escalabilidade: threads não ficam bloqueadas em cada espera. Paralelismo pode aumentar vazão. Declare a métrica antes de escolher o mecanismo.
Compartilhamento exige propriedade e coordenação
Antes de criar threads, liste quais dados são:
- imutáveis e compartilháveis com segurança;
- exclusivos de uma thread ou tarefa;
- transferidos de um produtor para um consumidor;
- compartilhados e mutáveis, exigindo protocolo explícito;
- externos, como arquivo ou banco, com coordenação própria.
Imutabilidade, particionamento e passagem de mensagens podem reduzir compartilhamento mutável. Quando ele é necessário, mutexes, semáforos, monitores, operações atômicas e filas oferecem contratos diferentes. Selecioná-los sem conhecer a invariável apenas desloca o erro.
Thread-safe também não significa “a operação composta é correta”. Uma coleção pode proteger cada método e ainda permitir que duas chamadas separadas observem um estado intermediário. Correção pertence à sequência e à regra do domínio, não somente ao tipo utilizado.
Observe threads como uma fotografia
No PowerShell, uma consulta somente leitura pode mostrar quantas threads cada processo expõe naquele instante:
Get-Process |
Sort-Object ProcessName |
Select-Object -First 5 Id, ProcessName,
@{Name = "Threads"; Expression = { $_.Threads.Count }}
O número pode mudar antes mesmo de a saída terminar. Ele não informa quantas threads estavam executando simultaneamente, quanto trabalho cada uma realizou nem quantas tarefas do runtime existem. Para isso, são necessários rastreamento temporal, contadores e ferramentas específicas da plataforma.
Não altere prioridade ou afinidade para “otimizar” a partir de uma listagem. Primeiro estabeleça hipótese, período representativo e métrica. Uma fotografia de muitas threads pode indicar pool ocioso, biblioteca interna, workers ativos ou vazamento; o contexto decide.
Erros comuns
- “Thread é um processo menor.” Ela pertence a um processo e compartilha sua moldura de recursos.
- “Toda variável é compartilhada.” Pilha e dados locais da thread são distintos; heap e globais costumam ser compartilhados.
- “Task, coroutine e thread são sinônimos.” Runtimes podem multiplexar tarefas sobre threads de várias formas.
- “Concorrente significa simultâneo.” Interleaving em uma CPU já permite concorrência sem paralelismo.
- “Paralelo significa assíncrono.” Um cálculo paralelo pode bloquear o chamador até todas as partes terminarem.
- “
asynccria uma thread.” A operação pode usar I/O não bloqueante e continuar depois sem thread dedicada durante a espera. - “Mais threads sempre aceleram.” Coordenação, contenção e recursos limitantes podem reduzir desempenho.
- “Thread-safe torna qualquer sequência correta.” Invariantes que abrangem várias operações continuam exigindo coordenação.
- “Uma thread esperando está usando a CPU.” Esperas bloqueantes normalmente retiram a thread da fila executável até o evento ocorrer.
- “Se funcionou uma vez, a ordem está garantida.” Sem contrato, o interleaving pode mudar entre execuções.
O que você deve guardar
Processo é a moldura de memória, recursos e proteção; thread é uma continuidade executável dentro dela. Threads compartilham código, heap e recursos, mas preservam identificador, registradores, pilha e outros atributos necessários para continuar corretamente.
Concorrência organiza trabalhos sobrepostos e pode existir por interleaving em uma CPU. Paralelismo exige execução simultânea. Assincronia separa início e conclusão, permitindo ceder controle durante uma espera; não implica automaticamente nova thread ou múltiplos núcleos.
Cargas de CPU e de I/O pedem estratégias diferentes. Mais threads podem ajudar, não mudar nada ou piorar o resultado. Dependências, isolamento, propriedade dos dados, limites e medição devem orientar o desenho.
Na próxima aula, entenda como o sistema escolhe entre trabalhos prontos e compare filas, prioridades e algoritmos em escalonamento de CPU.
Referências
- The Open Group — POSIX.1-2024: General Information, Threads. Acesso em 4 set. 2026.
- The Open Group — POSIX.1-2024: Definitions of Thread and Thread Lifetime. Acesso em 4 set. 2026.
- The Open Group — POSIX.1-2024:
pthread_create. Acesso em 4 set. 2026. - The Open Group — POSIX.1-2024:
pthread_join. Acesso em 4 set. 2026. - Linux man-pages —
pthreads(7). Acesso em 4 set. 2026. - Microsoft Learn — About Processes and Threads. Acesso em 4 set. 2026.
- Microsoft Learn — Thread Local Storage. Acesso em 4 set. 2026.
- Microsoft Learn — Asynchronous Programming Scenarios. Acesso em 4 set. 2026.
- Go Documentation — Effective Go: Concurrency. Acesso em 4 set. 2026.
- Go Blog — Concurrency is not Parallelism. Acesso em 4 set. 2026.
- ACM, IEEE-CS e AAAI — CS2023: Operating Systems. Acesso em 4 set. 2026.
