select! & Concorrência
tokio::select! multiplexa múltiplos branches assíncronos, completando quando o primeiro termina. É o padrão principal para loops de eventos, tratamento de desligamento e disputas de I/O com timers.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(4);
tokio::select! {
Some(msg) = rx.recv() => println!("{msg}"),
_ = sleep(Duration::from_millis(100)) => println!("timeout"),
else => println!("canal fechado"),
}
let _ = tx.send("oi").await;
}Quando usar isso: Loops principais de servidor, combinando mensagens de canal com sinais de desligamento e disputas de timeout.
Exemplo de Trabalho
use tokio::sync::mpsc;
use tokio::time::{interval, Duration};
async fn worker(mut rx: mpsc::Receiver<u32>, mut shutdown: mpsc::Receiver<()>) {
let mut ticker = interval(Duration::from_millis(200));
loop {
tokio::select! {
_ = shutdown.recv() => {
println!("desligando");
break;
}
Some(job) = rx.recv() => {
println!("tarefa {job}");
}
_ = ticker.tick() => {
println!("heartbeat");
}
}
}
}
#[tokio::main]
async fn main() {
let (job_tx, job_rx) = mpsc::channel(8);
let (stop_tx, stop_rx) = mpsc::channel(1);
let handle = tokio::spawn(worker(job_rx, stop_rx));
job_tx.send(1).await.unwrap();
stop_tx.send(()).await.unwrap();
handle.await.unwrap();
}O que isso demonstra:
- Loop de longa duração com múltiplas fontes de eventos.
- Branch de desligamento sai de forma limpa.
- Branch de tick de
intervalpara trabalho periódico.
Mergulho Profundo
Como Funciona
- Primeiro branch pronto vence: Outros branches são desabilitados para esta iteração.
- Cancelamento: Branches que perdem são descartados nos pontos de
await. - biased: Prefixo opcional
biased;verifica os branches na ordem de declaração (mais rápido, pode causar inanição). - else: Executa quando todos os branches estão desabilitados (ex: canal fechado).
Segurança de Cancelamento
| Operação | Segura para cancelamento em loop? |
|---|---|
mpsc::recv | sim |
sleep | sim |
read buffer parcial | frequentemente não - use o buffer com cuidado |
async fn com estado | depende - audite a documentação |
Notas de Rust
// Seleção aleatória justa (padrão para casos raros)
use rand::Rng;
let branches = [async { 1 }, async { 2 }];
// Prefira select! declarativo para legibilidade na maioria dos servidores- Documente quais branches são seguros para cancelar em um loop
select!. - Combine com
Semaphorepara limitar o trabalho selecionado concorrente. select!em múltiplosJoinHandles disputa a conclusão da tarefa.
Armadilhas
- Leitura não segura para cancelamento em loop - Leitura parcial perdida no cancelamento. Correção: Buffer de acordo com as regras do protocolo ou use uma tarefa de leitura dedicada.
- Inanição de
selectcombiased- O primeiro branch sempre pronto bloqueia os outros. Correção: Reordene ou removabiasedpara justiça. selectsem saída de loop - Loop rápido se o branch estiver imediatamente pronto. Correção: Adicionesleepou aguarde umrecvbloqueante.- Mutex entre
select- O guard não éSend. Correção: Adquira o lock dentro do corpo do branch apenas. elseesquecido em canais fechados - Loop em branches desabilitados. Correção: UseelseoubreakemNone.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
join! | Precisa de todos os branches | Loop de eventos primeiro a completar |
| Tarefa de leitura dedicada + mpsc | Análise complexa de protocolo | Timer simples + um canal |
FuturesUnordered | Muitos futures dinâmicos | Conjunto fixo pequeno de branches |
| epoll manualmente | Runtime customizado | Código de aplicação Tokio |
FAQs
select! vs join!?
select! primeira conclusão; join! espera por todos. Casos de uso diferentes.
O que `biased` faz?
Verifica os branches em ordem sem aleatorização - previsível, mas pode causar inanição nos branches posteriores.
Padrão de loop?
loop { select! { ... } } é padrão para serviços Tokio até que o branch de desligamento quebre.
Múltiplos canais?
Um branch por recv de canal - design clássico de servidor multiplexado.
select com streams?
Branch some_stream.next() => suportado em tokio::select!.
pin necessário?
Alguns futures de branch precisam de pin em select - siga os erros de expansão de macro com pin!.
Desligamento do Axum?
with_graceful_shutdown usa um future de sinal - relacionado, mas em nível de servidor.
testando select?
tokio::time::pause + advance para branches de timer determinísticos.
eventos de prioridade?
Coloque o branch de alta prioridade primeiro com biased - documente o risco de inanição.
select entre crates?
Use tokio::select! para futures Tokio; futures::select para contextos não-Tokio.
Relacionados
- Combinando Futures - teoria de join e select
- Canais em Tokio - branches recv
- Timers & Intervals - sleep e interval
- Desligamento Gracioso - select de desligamento
- Cancelamento & Timeouts - segurança de cancelamento
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.