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
  • Compiladores
  • Interpretadores
  • Vai um Javinha aí?
  • E por quê precisamos saber disto com relação a concorrência?
  • Ambientes de execução (runtimes)
  1. PARTE II - Concorrência em diferentes linguagens

Definindo ambientes de execução

PreviousVamos colocar em prática...NextConcorrência em C

Last updated 4 months ago

Antes de falarmos das diferenças entre algumas implementações de linguagens de programação, precisamos definir o que é uma linguagem de programação. De acordo com a Wikipedia:

A linguagem de programação é um método padronizado, formado por um conjunto de e semânticas, de implementação de um - que pode ser e transformado em um , ou usado como - que informará de processamento ao .

Ou seja, quando falamos em linguagem de programação, estamos nos referindo a uma especificação sintática e semântica que, através de um processo de compilação (ou interpretação), será transformado em código de máquina que o computador possa entender.

Para que uma linguagem seja executada no computador, é preciso ser implementada através de um programa, ou conjunto de programas e ferramentas, formando um "ambiente", onde será feito o processo de compilação, interpretação e/ou execução. Estas instruções irão utilizar recursos do sistema operacional que, por sua vez, irá manipular recursos de hardware, como CPU, memória e I/O.

Com isso conseguimos entender porque é tão importante a primeira parte deste guia, amém?

Compiladores

Compilador é um programa que, dado um input que é código-fonte que segue a especificação de uma linguagem, aplica transformações e gera um executável que contém código de máquina da arquitetura do computador ou um código intermediário.

Exemplos de linguagens que utilizam compiladores: C (GCC, Clang), Java (javac), Kotlin (kotlinc), Go (gc), Rust (rustc)

É possível haver mais de uma implementação para cada linguagem de programação. Vai depender da adesão, investimentos feitos entre outros aspectos.

Interpretadores

Interpretador é um programa que, dado um input que é código-fonte que segue a especificação de uma linguagem, aplica transformações e executa as instruções da arquitetura "em tempo real".

Exemplos de linguagens que utilizam interpretadores: Python (CPython), Ruby (CRuby), PHP (Zend), Javascript (V8), Java (JVM)

Assim como no caso dos compiladores, é possível haver mais de uma implementação de interpretador para cada linguagem:

Repare na principal diferença entre interpretador e compilador: o compilador gera um executável, enquanto que o interpretador vai traduzindo e executando diretamente. Há vantagens e desvantagens em ambos os casos, inclusive há diversos ambientes que fazem um misto de processo de compilação com um interpretador embutido.

Vai um Javinha aí?

Por exemplo, em Java, o compilador javac não gera um código de máquina da arquitetura diretamente, mas sim um "bytecode" intermediário. Então, o bytecode é executado por um interpretador que vem dentro do ambiente Java. A este ambiente de execução (runtime), que é capaz de interpretar os bytecodes gerados pelo javace traduzir para instruções da arquitetura do computador, chamamos de Java Virtual Machine, ou simplesmente JVM.

E por quê precisamos saber disto com relação a concorrência?

Como vimos na primeira parte do guia, o sistema operacional oferece recursos (chamadas de sistema, syscalls) para criar processos, threads e gerenciar recursos de I/O:

  • criação de processos e threads por meio da syscall clone

  • gerenciar recursos de I/O utilizando as syscalls read, write, open etc

  • gerenciar recursos de I/O assíncrono com as syscalls select, epoll, io_uring etc

Ou seja, as implementações de linguagens de alto nível precisariam fazer estas chamadas. E, pra nossa sorte, praticamente todas as linguagens mainstream (de propósito geral) fornecem formas de fazer estas chamadas ao sistema operacional.

Seria muito estranho uma linguagem não permitir a chamada de syscalls, né? Pra quê serviria esta linguagem então, se não fosse pra chamar syscalls no SO com o intuito de manipular recursos de hardware?

Ambientes de execução (runtimes)

Algumas implementações acabam por ter um conjuto de compilador com interpretador, como vimos no exemplo do Java. E não só, podem também trazer técnicas de alto nível para criação de "user threads" (que são threads que vivem apenas dentro do ambiente, e não threads do SO), garantindo a troca de contexto entre elas e sua execução, tudo dentro do ambiente.

Geralmente, estes ambientes se chamam runtimes. Poderíamos tecnicamente chamar tudo de runtime, mas em alguns casos isto não é precisamente correto, como no caso do GCC, que é apenas um conjunto de ferramentas para compilação de código C.

Entretanto, podemos acordar, neste guia, que iremos tratar tudo por "runtime" (ambientes de execução), não importa se é apenas um compilador, um interpretador, um híbrido, se implementa user threads ou não, enfim. Vou chamar tudo de runtime simplesmente.

Me perdoem, acadêmicos de plantão. Não é hoje que vocês vão me cancelar

Aqui vai uma lista das principais implementações de algumas linguagens que vamos abordar ao longo deste guia:

  • C (GCC)

  • Java JDK (javac + JVM)

  • Python (CPython)

  • Ruby (CRuby, ou MRI)

  • Javascript (V8, usado no Chrome e também no Node.js)

  • PHP (Zend)

  • Go (gc)

  • Rust (rustc)

  • Kotlin (kotlinc + JVM)

  • Elixir (BEAM)

Acho que com estas linguagens dá pra cobrir diversas funcionalidades de concorrência. Iremos entender as principais diferenças, casos de uso e suas vantagens/desvantagens. Vamos fazer uma viagem longa através dos processos, criação de threads no SO, criação de threads dentro do runtime, manipulação de I/O assíncrono entre outras coisas interessantes.

Aperte os cintos, porque agora sim, vamos começar a ver o negócio na prática!

regras sintáticas
código fonte
compilado
programa de computador
script
interpretado
instruções
computador