4 pontos por GN⁺ 2024-03-26 | 1 comentários | Compartilhar no WhatsApp
  • Na comunidade Rust, aparece com frequência a pergunta: se threads conseguem fazer tudo o que async/await faz e de forma mais simples, por que escolher async/await?
  • Rust é uma linguagem de baixo nível e não esconde a complexidade das corrotinas. Isso é o oposto de linguagens como Go, que funcionam de forma assíncrona por padrão sem exigir que o programador pense em assincronicidade.
  • Programadores inteligentes tentam evitar complexidade, então por que async/await é necessário?

Entendendo o contexto

  • Rust é uma linguagem de baixo nível. O código geralmente é linear, e uma tarefa só é executada depois que a outra termina.
  • Quando é preciso executar muitas tarefas ao mesmo tempo, como em um servidor web, o código linear passa a ser um problema.
  • A web inicial tentou resolver isso introduzindo threading.
  • Com threads, é possível atender vários clientes simultaneamente, mas os programadores também quiseram trazer a concorrência do espaço do sistema operacional para o espaço do usuário.

O problema do timeout

  • Uma das maiores vantagens do Rust é a composabilidade.
  • async/await permite aplicar essa composabilidade a funções limitadas por I/O.
  • Por exemplo, ao querer adicionar um timeout a uma função de atendimento de cliente, isso pode ser implementado usando dois combinadores.

Threads temáticas

  • Em um exemplo usando threads, implementar timeout não é algo simples.
  • TcpStream tem as funções set_read_timeout e set_write_timeout, mas o uso delas é limitado.
  • O artigo mostra uma forma de programar timeout usando os combinadores do Rust, mas isso fica restrito a TcpStream e ainda exige chamadas de sistema adicionais.

Casos de sucesso de async

  • O ecossistema HTTP adotou async/await como principal mecanismo de runtime.
  • tower é um exemplo que mostra a força de async/await, oferecendo timeout, limitação de taxa, balanceamento de carga e mais.
  • macroquad é uma engine de jogos em Rust que executa a engine usando async/await.

Melhorando a imagem do async

  • Como as vantagens de async não são amplamente conhecidas, algumas pessoas podem entendê-lo mal.
  • A comunidade Rust tende a superestimar os ganhos de desempenho do Rust assíncrono e a minimizar seus benefícios realmente significativos.
  • async/await deve ser visto como um modelo de programação poderoso, capaz de expressar de forma concisa padrões que, em Rust síncrono, não podem ser representados sem dezenas de threads e canais.

Opinião do GN⁺

  • async/await aumenta a complexidade do código ao lidar com concorrência, mas ao mesmo tempo oferece a capacidade de atender muitos clientes com eficiência.
  • Este artigo destaca que async/await tem pontos fortes no modelo de programação, e não apenas vantagens de desempenho.
  • O async/await do Rust oferece composabilidade para vários tipos de trabalho de I/O, o que é especialmente útil em áreas como serviços de rede e servidores web.
  • Sob uma visão crítica, a complexidade de async/await pode ser uma barreira de entrada para desenvolvedores iniciantes, e é preciso esforço educacional para superar isso.
  • Outros projetos com funcionalidade semelhante incluem a implementação de async/await do Node.js e a biblioteca asyncio do Python, que também oferecem paradigma parecido.
  • Ao adotar async/await, é preciso considerar a complexidade e a manutenibilidade do código; ainda assim, quando é necessário atender muitos clientes ao mesmo tempo, esse modelo oferece grandes vantagens.

1 comentários

 
GN⁺ 2024-03-26
Comentários do Hacker News
  • Async/await e thread única

    • Assim como no modelo do JavaScript, async/await em uma única thread é simples e bem compreendido.
    • Com threads, vários CPUs podem processar o problema, e Rust ajuda no gerenciamento de locks.
    • É possível ter threads com prioridades diferentes, o que é necessário quando há restrições de computação.
    • Async/await multithread é complexo. Em seções com limitação de computação, o modelo pode desmoronar.
    • Em Rust, computação multithread não funciona bem. Entre os problemas:
      • Colapso por contenção de futex: pode ser um problema em alguns alocadores de storage.
      • Inanição com mutex injusto: o Mutex padrão e os canais crossbeam-channel são injustos.
  • Async/await versus threads

    • A crítica não é sobre complexidade, mas sobre o fato de que a escolha divide o ecossistema e torna uma das opções inferior.
    • O ecossistema Rust decidiu que, para fazer trabalho de I/O, é preciso usar async/await em tudo.
    • Se Rust tivesse tornado alternativas ao async/await abstrações mais combináveis, a insatisfação teria desaparecido.
  • Problemas no artigo

    • Foi apresentado apenas um exemplo de servidor web, e ele resolve mal a questão das threads.
    • Programadores querem threads conceituais, semânticas, não threads do sistema operacional.
    • Threads do sistema operacional são caras; queremos threads baratas.
    • Há problemas na implementação de timeout no exemplo do servidor web.
  • Pontos que não foram abordados

    • Async/await roda em uma única thread, então não exige locks nem sincronização.
    • A propagação de erros em async/await não é clara.
    • Backpressure em I/O de rede também deveria ter sido mencionado.
  • Ponto importante sobre cancelamento

    • Qualquer tarefa futura pode ser cancelada com facilidade.
    • O cancelamento em threads é complexo, e a interrupção forçada de threads não é confiável.
    • No modelo async do Rust, é possível adicionar timeout externamente a todos os futures.
  • Uma campanha quase de marketing em torno de async/await

    • Async/await foi um erro técnico e trouxe um grande custo para a comunidade.
    • Rust continua sendo a melhor linguagem, mas há preocupação de que esse debate dure para sempre.
  • Async/await versus fibras

    • Rust já teve green threads antes, e elas foram removidas de propósito.
    • A capacidade de descartar futures a qualquer momento traz um custo elevado.
    • É estranho elogiar a combinabilidade de async/await.
  • Principais vantagens do async/await em Rust

    • Pode funcionar mesmo em contextos sem threads ou memória dinâmica.
    • Permite escrever código de forma concisa usando concorrência.
  • Mal-entendidos sobre async/await

    • Há quem não entenda por que é necessário um mecanismo de concorrência em thread única.
    • Async/await é útil em programação de UI, comunicação com GPU e comunicação entre runtimes.
  • Por que escolher async/await em vez de threads

    • Async/await pode reduzir o uso de memória do estado de cliente/requisição/tarefa.
    • A compactação de estado é importante para desempenho em um mundo moderno com memória lenta.
    • Async/await e CPS são eficazes para reduzir o uso de memória por cliente.