concorrencia101
  • Introdução
  • First things first
  • Agradecimentos
  • Parte I - Concorrência no sistema operacional
    • O que é o programa no sistema operacional
    • Escalonador preemptivo de tarefas
    • Uma nota sobre escalonamento cooperativo
    • Propriedades de um processo
    • Clone de processo (forking)
    • Clone leve de processo (thread)
    • Todo processo tem uma thread principal
    • Uma nota sobre paralelismo
    • Principais desafios em cenário de concorrência
      • Race condition
      • Sincronização com locks
      • Modelo de atores
    • E o I/O?
      • Latência de CPU vs Latência de I/O
      • Chamadas bloqueantes
      • Chamadas não-bloqueantes
      • Assincronismo e escalonamento cooperativo
    • Vamos colocar em prática...
  • PARTE II - Concorrência em diferentes linguagens
    • Definindo ambientes de execução
    • Concorrência em C
      • Forking de processos
      • Threads
      • Race condition e sincronização de threads com mutex
      • Desafios com o uso de threads
      • Thread Pool em C
      • Green threads
      • Modelo de Atores
      • Trabalhando com I/O
    • Concorrência em Ruby
      • Forking de processos
      • Threads
      • Race condition, YARV, GVL e paralelismo em Ruby
      • Modelo de Atores
      • Trabalhando com I/O
Powered by GitBook
On this page
  1. PARTE II - Concorrência em diferentes linguagens
  2. Concorrência em C

Desafios com o uso de threads

PreviousRace condition e sincronização de threads com mutexNextThread Pool em C

Last updated 4 months ago

O uso de threads, apesar de ser mais leve que forking de processos, traz também alguns desafios. A criação da thread pthread_create tem um custo no sistema operacional, como podemos imaginar. O join também, e o pthread_mutex_lock e unlock não ficam de fora.

Todas essas chamadas de funções com relação ao uso de threads causam um overhead no sistema como um todo, faz o escalonador trabalhar mais através de múltiplas trocas de contexto, e isso contribui para o aumento da latência total do sistema. Essa troca de contexto é custosa, pois envolve salvar e restaurar o estado da thread (registradores, pilha, etc) e pode causar cache misses no processador.

O sistema operacional implementa diversas otimizações para gerenciar threads de forma eficiente, mas mesmo assim, o uso indiscriminado pode levar a problemas de desempenho e complexidade.

Trabalhar com threads é complexo

Quando usar threads com cautela?

Criar muitas threads em um sistema com recursos limitados pode causar degradação de desempenho devido ao aumento do overhead de gerenciamento das threads.

Por exemplo, em um sistema com 4 núcleos de CPU, criar centenas de threads geralmente não traz ganho adicional, pois a maioria delas ficará em espera devido a sincronização com mutexes. Múltiplas trocas de contexto irão piorar ainda mais o desempenho.

Para além de race conditions, podemos também enfrentar problemas muito difíceis como deadlocks, que são comuns em sistemas multithreaded e difíceis de depurar.

Em algumas situações, como por exemplo quando a T1 tem o acesso ao mutex, mas por algum motivo esse mutex ficou perdido na memória, então ela não consegue recuperar o mutex e portanto o recurso fica bloqueado para todas as outras threads, que estão à espera. Este efeito causa um deadlock, que lança um erro fatal no programa que o faz terminar imediatamente.

Por último, o sistema operacional determina uma quantidade limitada de threads. Isto por si só já nos limita bastante se precisarmos de disparar milhares de threads, como no caso de requisições HTTP em um web server. Este limite pode ser definido pelo utilitário ulimit em sistemas UNIX-like, e é configurado tanto a nível de usuário quanto de processo.

Todas estas limitações nos levam a:

  • criar uma abstração de "pool" (ou piscina), onde múltiplas threads previamente criadas podem ser utilizadas e devolvidas ao pool, assim não precisamos criar milhares de threads indiscriminadamente;

  • recorrer a alternativas "thread-safe", que garantem de alguma forma que a memória não é compartilhada entre as threads; ou

  • implementar um mecanismo de "green threads", onde pequenas unidades de concorrência vivem dentro do runtime (implementação), e não necessariamente no sistema operacional. Isto requer a implementação de um escalonador para essas green threads

Vamos primeiramente abordar a implementação de uma pool de threads e como isto pode mitigar bastante os problemas inerentes ao uso de threads.

exemnplo de deadlock na vida real kkk