La programación concurrente es un tema fundamental en la creación de aplicaciones eficientes y escalables. En el lenguaje ZIG, la gestión de hilos (threads) es una característica clave para aprovechar al máximo los recursos del sistema. Sin embargo, la sincronización entre hilos puede ser un desafío, ya que requiere asegurarse de que los hilos accedan a los recursos compartidos de manera segura y coordinada.
Introducción a la sincronización
La sincronización se refiere a la coordinación de acceso a recursos compartidos por parte de múltiples hilos. Esto es necesario para evitar problemas como la condición de carrera (race condition), donde la ejecución de dos o más hilos puede interferir con la ejecución de otros hilos, llevando a resultados inconsistentes o incorrectos.
Mecanismos de sincronización en ZIG
ZIG proporciona varios mecanismos para sincronizar el acceso a recursos compartidos, incluyendo:
- Mutex (Mutual Exclusion): permite a un solo hilo acceder a un recurso compartido en un momento dado.
- Condición: permite a un hilo esperar a que se cumpla una condición específica antes de continuar la ejecución.
- Señales (Signals): permiten a un hilo enviar una señal a otro hilo para notificar un evento específico.
Ejemplo de sincronización con Mutex
A continuación, se muestra un ejemplo de cómo sincronizar el acceso a un recurso compartido utilizando un Mutex en ZIG:
const std = @import("std");
pub fn main() !void {
// Crear un Mutex
var mutex = std.sync.Mutex.init();
// Crear un hilo que accede al recurso compartido
const thread = try std.Thread.spawn(try std.heap.page_allocator.alloc(u8, 1024), struct {
fn threadFunc(allocator: std.mem.Allocator) !void {
// Adquirir el Mutex
mutex.lock();
defer mutex.unlock();
// Acceder al recurso compartido
std.debug.print("Hilo 1: Accediendo al recurso compartido\n", .{});
}
}.threadFunc, {});
// Crear otro hilo que accede al mismo recurso compartido
const thread2 = try std.Thread.spawn(try std.heap.page_allocator.alloc(u8, 1024), struct {
fn threadFunc(allocator: std.mem.Allocator) !void {
// Adquirir el Mutex
mutex.lock();
defer mutex.unlock();
// Acceder al recurso compartido
std.debug.print("Hilo 2: Accediendo al recurso compartido\n", .{});
}
}.threadFunc, {});
// Esperar a que los hilos terminen
thread.join();
thread2.join();
}
En este ejemplo, se crea un Mutex y se utilizan dos hilos para acceder a un recurso compartido. El Mutex se adquiere antes de acceder al recurso compartido y se libera después de finalizar la operación. De esta manera, se garantiza que solo un hilo puede acceder al recurso compartido en un momento dado.
Ejemplo de sincronización con Condición
A continuación, se muestra un ejemplo de cómo sincronizar el acceso a un recurso compartido utilizando una Condición en ZIG:
const std = @import("std");
pub fn main() !void {
// Crear una Condición
var cond = std.sync.Condvar.init();
// Crear un hilo que espera a la Condición
const thread = try std.Thread.spawn(try std.heap.page_allocator.alloc(u8, 1024), struct {
fn threadFunc(allocator: std.mem.Allocator) !void {
// Esperar a la Condición
cond.wait();
std.debug.print("Hilo: La Condición se ha cumplido\n", .{});
}
}.threadFunc, {});
// Crear otro hilo que cumple la Condición
const thread2 = try std.Thread.spawn(try std.heap.page_allocator.alloc(u8, 1024), struct {
fn threadFunc(allocator: std.mem.Allocator) !void {
// Cumplir la Condición
cond.signal();
std.debug.print("Hilo 2: La Condición se ha cumplido\n", .{});
}
}.threadFunc, {});
// Esperar a que los hilos terminen
thread.join();
thread2.join();
}
En este ejemplo, se crea una Condición y se utilizan dos hilos para sincronizar el acceso a un recurso compartido. Un hilo espera a la Condición, mientras que otro hilo la cumple. De esta manera, se garantiza que el hilo que espera a la Condición no continue la ejecución hasta que la Condición se haya cumplido.
