Bloqueando o executor

A maioria dos runtimes async só permite que tarefas de I/O sejam executadas concorrentemente. Isso significa que tarefas que bloqueiam a CPU bloquearão o executor e impedirão que outras tarefas sejam executadas. Uma solução fåcil é usar métodos equivalentes async sempre que possível.

use futures::future::join_all;
use std::time::Instant;

async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(duration_ms));
    println!(
        "_future_ {id} dormiu por {duration_ms}ms, terminou apĂłs {}ms",
        start.elapsed().as_millis()
    );
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let start = Instant::now();
    let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10));
    join_all(sleep_futures).await;
}
  • Execute o cĂłdigo e veja que os sleeps acontecem consecutivamente em vez de concorrentemente.

  • A variante "current_thread" coloca todas as tarefas em um Ășnico thread. Isso torna o efeito mais Ăłbvio, mas o bug ainda estĂĄ presente na variante multi-threaded.

  • Troque o std::thread::sleep por tokio::time::sleep e aguarde seu resultado.

  • Outra correção seria tokio::task::spawn_blocking que inicia um thread real e transforma seu handle em uma future sem bloquear o executor.

  • VocĂȘ nĂŁo deve pensar em tarefas como threads do SO. Elas nĂŁo mapeiam 1 para 1 e a maioria dos executors permitirĂĄ que muitas tarefas sejam executadas em um Ășnico thread do SO. Isso Ă© particularmente problemĂĄtico ao interagir com outras bibliotecas via FFI, onde essa biblioteca pode depender de armazenamento local de thread ou mapear para threads especĂ­ficos do SO (por exemplo, CUDA). Prefira tokio::task::spawn_blocking em tais situaçÔes.

  • Use mutexes sync com cuidado. Manter um mutex sobre um .await pode fazer com que outra tarefa bloqueie, e essa tarefa pode estar sendo executada no mesmo thread.