Los canales std::sync::mpsc implementan el modelo de comunicación de “Múltiples Productores, Un Solo Consumidor” (Multiple Producer, Single Consumer). En lugar de compartir memoria mediante bloqueos (Mutex), este modelo fomenta el paso de mensajes: un hilo “envía” la propiedad de un dato a otro hilo a través de un canal. Esto evita condiciones de carrera y simplifica el razonamiento sobre la concurrencia al seguir la filosofía: “No comuniques compartiendo memoria; comparte memoria comunicando”.
Un canal se compone de dos extremos: un Sender<T> (emisor) y un Receiver<T> (receptor). El Sender puede clonarse para permitir que múltiples hilos actúen como productores. Sin embargo, el Receiver no es clonable, lo que garantiza que solo un hilo sea el encargado de procesar los mensajes.
Existen dos tipos de canales:
1. Canales asíncronos (mpsc::channel): Tienen un buffer ilimitado. El emisor nunca se bloquea al enviar, lo cual es eficiente pero puede agotar la memoria si el productor es mucho más rápido que el consumidor.
2. Canales síncronos (mpsc::sync_channel(n)): Tienen un buffer limitado a n elementos. Si el buffer se llena, el Sender se bloquea hasta que el receptor consuma un elemento, implementando un mecanismo de contrapresión (backpressure).
Si se intenta enviar un mensaje a un canal donde el receptor ha sido destruido, send devolverá un error. Del mismo modo, si el receptor intenta recibir datos pero todos los Sender han sido eliminados (dropped), la operación se cerrará.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// Se crea un canal de comunicación asíncrono.
// tx: Sender (emisor), rx: Receiver (receptor).
let (tx, rx) = mpsc::channel();
// Clonamos el emisor para crear un segundo productor.
// Esto es lo que permite el "Multiple Producer".
let tx_clon = tx.clone();
// Hilo 1: Produce los primeros números.
thread::spawn(move || {
let valores = vec![1, 2, 3];
for val in valores {
tx_clon.send(val).unwrap(); // Enviamos el valor al canal.
thread::sleep(Duration::from_millis(500));
}
// Al terminar este hilo, tx_clon se destruye.
});
// Hilo 2: Produce otros números usando el emisor original.
thread::spawn(move || {
let valores = vec![4, 5, 6];
for val in valores {
tx.send(val).unwrap(); // Enviamos el valor al canal.
thread::sleep(Duration::from_millis(100));
}
// Al terminar este hilo, tx se destruye.
});
// El hilo principal actúa como el único consumidor.
// El bucle for iterará sobre el receptor hasta que todos los
// Sender (tx y tx_clon) hayan sido droppeados.
for mensaje in rx {
println!("Consumido: {}", mensaje);
}
println!("Canal cerrado. Fin de la ejecución.");
}
Explicación del Código
mpsc::channel(): Inicializa el canal devolviendo una tupla que contienetx(de tipoSender<i32>) yrx(de tipoReceiver<i32>).tx.clone(): Crea una nueva instancia del emisor. En el ejemplo,tx_clones una copia detxque permite que el primer hilo tenga su propio manejador para enviar datos.thread::spawn(move || { ... }): Crea un hilo nuevo. El uso demovees crucial para transferir la propiedad detx_clonotxal interior del hilo.tx_clon.send(val).unwrap(): Envía el valorvalal canal. Si el canal estuviera cerrado,unwrap()causaría un pánico.rx: El iteradorfor mensaje in rxes la forma idiomática de consumir un canal. El hilo principal se queda bloqueado en elrecv()interno de este iterador hasta que llega un mensaje.- Cierre del canal: El bucle
fortermina automáticamente cuando todos los emisores (tx_clonytx) salen de su ámbito (scope) o son destruidos. Una vez que no hay más hilos capaces de enviar,rxsabe que no habrá más mensajes y finaliza la iteración.
N° 81