Patrones clásicos

Pipeline y cintas

Una línea de producción es un pipeline natural: cada pieza pasa por las etapas 1, 2, 3… en orden, y cada etapa trabaja a la vez sobre piezas distintas. El truco para que el paralelismo sea real es que cada etapa sea un hilo y cada junta, una pareja productor/consumidor.

La instalación en acción

Una cadena de tres máquinas conectadas por cintas (colas): la etapa 2 es más lenta y hace de cuello de botella. Arranca la simulación y observa cómo las colas de su entrada se llenan y la presión sube hacia la tolva (backpressure).

Consejo: fíjate en qué hilo queda bloqueado primero cuando la línea se satura.

El esquema general

Para K etapas se necesitan K relaciones productor/consumidor: la de entrada de la primera etapa y la de cada junta. En el estilo clásico, dos vectores de semáforos:

// sp[i] = hueco libre a la entrada de la etapa i (inicial = capacidad)
// sc[i] = pieza presente a la entrada de la etapa i (inicial = 0)
Semaforo sp[K];   // todos a su capacidad
Semaforo sc[K];   // todos a 0

Cada etapa i ejecuta siempre el mismo esqueleto:

void hEtapa(int i) {
    while (true) {
        sc[i].wait();          // 1) espera la pieza de la etapa anterior
        procesar(i);           // 2) trabaja (la etapa anterior ya puede seguir)
        sp[i + 1].wait();      // 3) espera hueco en la siguiente
        transferir(i, i + 1);  // 4) mueve la pieza
        sp[i].signal();        // 5) libera su entrada
        sc[i + 1].signal();    // 6) avisa a la siguiente etapa
    }
}

El orden de los pasos 5 y 6 es indiferente, pero el paso 3 debe ir después del procesado: si la etapa esperara hueco para soltar antes de terminar su trabajo, la pieza anterior quedaría retenida y el pipeline perdería paralelismo.

El transfer compartido: región crítica

En muchas máquinas (ej61 del cuaderno) un único sistema transfer mueve las piezas entre posiciones. Como lo necesitan todas las etapas a la vez, es región crítica:

Semaforo sTransfer(1);       // el transfer es de uno en uno

void mueve(int origen, int destino) {
    sTransfer.wait();
    transfiere(origen, destino);
    sTransfer.signal();
}

Igual se trata cualquier recurso de transporte único: el robot de la célula FMS, la grúa del pulmón, el carro electroimán entre cintas.

Cintas transportadoras entre etapas

Cuando entre dos máquinas hay una cinta, la cinta también tiene su hilo, que hace de consumidor de la máquina anterior y productor de la siguiente:

void hCinta(int c) {
    while (true) {
        cinta(c, 0);                    // parada por defecto (ahorro)
        sCintaP[c].wait();              // ¿puedo recibir? (hueco en la cinta)
        sCintaC[c - 1].wait();          // ¿hay pieza esperándome?
        cinta(c, 1);                    // en marcha
        while (!barrera(c + 1));        // hasta que la pieza llegue al final
        cinta(c, 0);                    // parar
        sMaquinaC[c].signal();          // avisa a la máquina de delante
        sMaquinaP[c - 1].signal();      // libera a la máquina de detrás
    }
}
📘 Nota

El while (!barrera(c+1)); es una espera por sondeo al hardware; en un sistema con semáforos ligados a interrupciones se sustituiría por un wait. El cuaderno lo deja como sondeo porque modela la lectura directa del sensor.

Ritmo y cuello de botella

El throughput de un pipeline lo marca su etapa más lenta. Si la etapa 2 tarda el doble, las etapas 1 y 3 pasarán la mitad del tiempo bloqueadas en sp[2].wait() / sc[1].wait(). Consecuencias de diseño: