Saltar al contenido principal

Práctica 1.1: Creación y Gestión de Procesos e Hilos

Fecha límite de entrega: 13 de octubre de 2026 a las 15:00
Lenguajes de trabajo: C (POSIX) y Rust
Código de inicio: 📥 mandelbrot_base.zip


1. Objetivos de la Práctica

  1. Comprender la diferencia física entre Memoria Compartida y Memoria Aislada: Experimentar el acceso nativo a memoria de los hilos (pthread) frente al aislamiento de los procesos (fork()), resolviéndolo explícitamente con Memoria Compartida POSIX (mmap).
  2. Identificar y resolver el Desequilibrio de Carga (Load Imbalance): Descubrir cómo la complejidad heterogénea del algoritmo afecta al rendimiento al usar un Reparto Estático por Bloques frente a un Reparto Intercalado / Cíclico.
  3. Automatización de procesos con exec: Crear procesos hijos y reemplazar su imagen ejecutable mediante execvp() para invocar herramientas del sistema tras el cálculo.
  4. Concurrencia en Rust: Implementar la versión multihilo en Rust usando std::thread::spawn y comparar la ergonomía y seguridad del lenguaje.

2. Código base y visualización

Como caso de estudio para comparar la aceleración y la gestión de memoria, generaremos el fractal del conjunto de Mandelbrot (zn+1=zn2+cz_{n+1} = z_n^2 + c).

Ejemplo de imagen generada del conjunto de Mandelbrot

Descarga del código de partida

Descarga el paquete comprimido con el código secuencial de partida tanto para C como para Rust:
📥 mandelbrot_base.zip

El archivo contiene:

  • mandelbrot_base.c: Código base secuencial en C.
  • mandelbrot_rust/: Proyecto de Cargo base secuencial en Rust.

Compilación y ejecución secuencial en C:

gcc -O2 mandelbrot_base.c -o mandelbrot_base -lm
./mandelbrot_base

Compilación y ejecución secuencial en Rust:

cd mandelbrot_rust
cargo run --release

Ambos programas generan una imagen mandelbrot.ppm en formato bruto PPM (Portable Pixmap).

Visualización y Conversión a PNG

Para poder visualizar fácilmente el resultado en cualquier visor de imágenes o navegador, puedes convertir manualmente el archivo .ppm a .png ejecutando en tu terminal:

ffmpeg -y -i mandelbrot.ppm mandelbrot.png

3. Ejercicios

Ejercicio 1: Implementación multi-hilo con hilos POSIX (pthread)

En este ejercicio implementarás una versión paralela en C usando la librería POSIX Threads (pthread.h) en el archivo mandelbrot_hilos.c. Todos los hilos compartirán el buffer global de píxeles Pixel img[HEIGHT][WIDTH].

Requisitos del programa:

  1. Debe recibir el número de hilos NN por línea de comandos (./mandelbrot_hilos 4).
  2. Debe distribuir las filas del fractal entre los NN hilos. Para comprobar el rendimiento en tu memoria, debes probar dos estrategias de reparto:
    • Reparto estático por bloques: El Hilo 0 procesa las primeras HEIGHTN\frac{HEIGHT}{N} filas, el Hilo 1 el siguiente bloque, etc.
    • Reparto intercalado/cíclico: El Hilo hh procesa las filas yy donde y(modN)==hy \pmod N == h (Round-Robin).
  3. Conversión automática con fork-exec: Tras guardar el archivo mandelbrot.ppm en disco, mediante los comandos fork y execvp llama a fmmpeg automáticamente mandelbrot.png. El proceso padre esperará la terminación del hijo de conversión mediante wait(NULL).

Cuestiones:

  1. ¿Por qué el reparto cíclico consigue un tiempo de ejecución significativamente menor que el reparto por bloques? (Piensa en la diferencia de iteraciones entre los píxeles del centro negro del fractal y los píxeles del fondo).
  2. Calcula el Speedup (SN=T1TNS_N = \frac{T_1}{T_N}) para N=2,4,8N=2, 4, 8 hilos en tu procesador y represéntalo en una tabla en tu memoria.
  3. ¿Qué ocurre con las líneas de código situadas después de la llamada a execvp() en el proceso hijo de conversión? Explica por qué nunca llegan a ejecutarse si la llamada tiene éxito.

Ejercicio 2: Implementación multi-proceso con fork y memoria compartida

En este ejercicio implementarás el paralelismo usando procesos independientes con fork() en el archivo mandelbrot_procesos.c.

Apartado A: El Problema de la Memoria Aislada

Si intentas duplicar el código de hilos sustituyendo pthread_create por fork(), comprobarás que la imagen resultante sale completamente negra o vacía.

  • Explicación: Al llamar a fork(), el kernel clona el espacio de direcciones del padre (Copy-On-Write). Cuando un hijo escribe en img[y][x], modifica su copia privada en memoria. El proceso padre conserva la memoria intacta.

Apartado B: Configuración de memoria compartida (shm_open + mmap)

Para que los NN procesos hijos puedan escribir en la matriz de la imagen final del padre:

  1. Crea un segmento de memoria compartida con shm_open("/mandel_shm", O_CREAT | O_RDWR, 0666).
  2. Establece su tamaño con ftruncate(fd, sizeof(Pixel) * HEIGHT * WIDTH).
  3. Mapea la región en el espacio de direcciones del proceso mediante mmap() especificando el flag MAP_SHARED:
    Pixel *img_shm = mmap(NULL, sizeof(Pixel) * HEIGHT * WIDTH,
    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  4. El proceso padre lanza los NN hijos con fork(), los cuales heredan el puntero img_shm.
  5. Cada hijo calcula su franja de filas y escribe directamente en img_shm.
  6. El proceso padre espera a que todos los hijos terminen mediante wait(NULL).
  7. El padre guarda la imagen .ppm, la convierte a .png con execvp, desmapea la memoria con munmap() y la elimina con shm_unlink("/mandel_shm").

Cuestiones:

  1. Explica la diferencia entre el acceso a memoria compartida en pthread (implícito) y en fork() con mmap (explícito).
  2. ¿Qué ocurriría si se omitiera el flag MAP_SHARED en la llamada a mmap()?

Ejercicio 3: Multi-Hilo en Rust (std::thread)

En este ejercicio implementarás la versión paralela del algoritmo en Rust dentro del proyecto mandelbrot_rust (en el archivo src/main.rs).

Requisitos del programa:

  1. Debe recibir el número de hilos NN como argumento por línea de comandos (cargo run --release -- 4).
  2. Utiliza std::thread::spawn para lanzar NN hilos que procesen las filas de la imagen.
  3. Utiliza la cláusula move para pasar los datos necesarios a la closure de cada hilo y espera la finalización de todos los hilos con .join().unwrap().
  4. Guarda la imagen resultante.

Cuestiones:

  1. Compara la sintaxis y el manejo de hilos en Rust frente a C (pthread). ¿Qué garantías ofrece el compilador de Rust (Borrow Checker) durante el desarrollo?

4. Entrega y Estructura del Repositorio

Debes entregar un archivo comprimido p1_1.tgz en la plataforma de prácticas conteniendo:

p1_1/
├── mandelbrot_hilos.c
├── mandelbrot_procesos.c
├── mandelbrot_rust/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── memoria.pdf (o memoria.md)

En la memoria debes incluir las respuestas a todas las Cuestiones planteadas en cada ejercicio.