¡Bienvenidos de nuevo a Rustaceo.es! En las entregas anteriores, construimos un servidor web en Rust que puede manejar rutas y responder dinámicamente a solicitudes HTTP. Ahora es momento de optimizar el rendimiento permitiendo que maneje múltiples conexiones simultáneamente utilizando multihilo.
🚀 Objetivos de esta parte #
- Implementar un pool de threads para manejar múltiples solicitudes concurrentes.
- Mejorar la eficiencia evitando bloqueos en conexiones entrantes.
- Asegurar la seguridad de los datos compartidos.
⚙️ Introducción al multihilo en Rust #
Por defecto, nuestro servidor maneja una conexión a la vez, lo que lo hace ineficiente cuando hay múltiples clientes. Para solucionarlo, crearemos un pool de threads, lo que nos permitirá procesar varias peticiones simultáneamente.
🏗 Implementando un pool de threads #
Comenzaremos definiendo una estructura para administrar un grupo de threads que procesarán las conexiones entrantes.
📌 Definiendo la estructura del pool #
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
impl ThreadPool {
fn nuevo(tamano: usize) -> ThreadPool {
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(tamano);
for id in 0..tamano {
workers.push(Worker::nuevo(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
fn ejecutar<F>(&self, tarea: F)
where F: FnOnce() + Send + 'static {
let tarea = Box::new(tarea);
self.sender.send(tarea).unwrap();
}
}
type Job = Box<dyn FnOnce() + Send + 'static>;
Aquí:
ThreadPool::nuevo(tamano)
: Crea un conjunto de workers que estarán esperando tareas.mpsc::channel()
: Permite comunicación entre el servidor y los threads.Arc<Mutex<T>>
: Facilita el acceso seguro a los trabajos desde múltiples threads.
🔄 Creando los workers #
Cada worker representará un hilo que ejecutará tareas cuando estén disponibles.
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn nuevo(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let tarea = receiver.lock().unwrap().recv().unwrap();
println!("Worker {} ejecutando tarea", id);
tarea();
}
});
Worker { id, thread: Some(thread) }
}
}
- Cada
Worker
toma una tarea del receiver y la ejecuta. thread::spawn()
permite que cada worker funcione en paralelo.loop
asegura que los workers sigan ejecutando nuevas tareas mientras el servidor esté en ejecución.
🔗 Integrando el pool en el servidor #
Ahora modificamos main.rs
para usar nuestro ThreadPool y manejar múltiples conexiones simultáneamente.
use std::net::TcpListener;
use std::io::{Read, Write};
use std::net::TcpStream;
fn manejar_conexion(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
let respuesta = "HTTP/1.1 200 OK\r\n\r\n¡Servidor Pokémon Multihilo!";
stream.write(respuesta.as_bytes()).unwrap();
stream.flush().unwrap();
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::nuevo(4);
println!("Servidor Pokémon Multihilo en http://127.0.0.1:7878");
for stream in listener.incoming() {
let stream = stream.unwrap();
pool.ejecutar(move || {
manejar_conexion(stream);
});
}
}
🛠 Explicación de los cambios #
- Creamos un pool de 4 threads usando
ThreadPool::nuevo(4)
. - Cada conexión es manejada por un worker, en lugar de bloquear el programa principal.
- Mejoramos el rendimiento y la escalabilidad al permitir múltiples clientes concurrentes.
🏆 Conclusión #
Con esta implementación, nuestro Servidor Pokémon ahora puede manejar múltiples solicitudes de manera concurrente, haciéndolo más rápido y escalable.
🎯 Resumen de lo aprendido #
✔ Implementamos un pool de threads para manejar múltiples conexiones. ✔ Utilizamos Rust seguro con Arc<Mutex<T>>
para gestionar tareas concurrentes. ✔ Integración en un servidor funcional que ahora soporta múltiples clientes simultáneamente.
🔮 Próximo paso: Puedes seguir mejorando el servidor añadiendo manejo de errores, soporte para HTTPS o un backend de almacenamiento para guardar datos de los Pokémon.
¡Hasta la próxima, Rustaceos!