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
portokio::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.