// throttle.c — un semáforo hecho con un lock y una CV (un "Zemaphore")
// usado para limitar cuántos hilos entran a la vez a una región costosa.
//
// Uso: ./throttle T K
//   T hilos quieren entrar; el Zemaphore empieza en K.
//   Se mide el máximo de hilos que estuvieron dentro a la vez.
#include "trace.h"

typedef struct {
    int value;
    pthread_cond_t cond;
    pthread_mutex_t lock;
} zem_t;

static void zem_init(zem_t *s, int value) {
    s->value = value;
    pthread_cond_init(&s->cond, NULL);
    pthread_mutex_init(&s->lock, NULL);
}

static void zem_wait(zem_t *s) {
    pthread_mutex_lock(&s->lock);
    while (s->value <= 0)               // nunca baja de 0
        pthread_cond_wait(&s->cond, &s->lock);
    s->value--;
    pthread_mutex_unlock(&s->lock);
}

static void zem_post(zem_t *s) {
    pthread_mutex_lock(&s->lock);
    s->value++;
    pthread_cond_signal(&s->cond);
    pthread_mutex_unlock(&s->lock);
}

static zem_t throttle;
static atomic_int inside, max_inside;

static void *worker(void *arg) {
    (void)arg;
    for (int i = 0; i < 5; i++) {
        zem_wait(&throttle);
        int now = atomic_fetch_add(&inside, 1) + 1;
        int old = atomic_load(&max_inside);
        while (now > old && !atomic_compare_exchange_weak(&max_inside, &old, now))
            ;
        msleep(5);                      // región costosa (p. ej., mucha memoria)
        atomic_fetch_sub(&inside, 1);
        zem_post(&throttle);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "uso: %s T K\n", argv[0]);
        return 1;
    }
    int nt = atoi(argv[1]), k = atoi(argv[2]);
    pthread_t t[nt];
    zem_init(&throttle, k);
    trace_start();
    for (int i = 0; i < nt; i++)
        pthread_create(&t[i], NULL, worker, NULL);
    for (int i = 0; i < nt; i++)
        pthread_join(t[i], NULL);
    printf("%d hilos, límite %d: máximo dentro a la vez = %d (%ld ms)\n",
           nt, k, atomic_load(&max_inside), now_ms());
    return 0;
}
