A Semaphore from std/sync limits the number of concurrent accesses to a resource. sem_new(n) creates a semaphore with n permits, sem_acquire takes a permit (blocking if none available), and sem_release returns one.
// Semaphore — limiting concurrent access to a resource // Semáforo — limitar acceso concurrente a un recurso import "std/sync" // Stateful handles (Semaphore/WaitGroup/mutex_new()) are SAFE as global // initializers — they run single-threaded before main (since v0.24.27). // Never lazy-init them behind a READY flag: that's a data race (see LLM.md). // ES: los handles con estado son SEGUROS como inicializadores globales — // corren single-threaded antes de main. Nunca lazy-init detrás de un flag. var sem: Semaphore = sem_new(2) var done: WaitGroup = wg_new() fn access_resource() -> int { sem_acquire(sem) print("acquired (count: " + int_to_string(sem_count(sem)) + ")") sleep(50) sem_release(sem) print("released") wg_done(done) return 0 } fn main() -> int { // Allow at most 2 concurrent accesses wg_add(done, 5) var i: int = 0 while i < 5 { thread_spawn(access_resource) i = i + 1 } wg_wait(done) print("all done") return 0 }
acquired (count: 1) acquired (count: 0) released released acquired (count: 1) acquired (count: 0) released released acquired (count: 1) released all done
How it works
sem_new(2) creates a semaphore with 2 permits. At most 2 threads can hold a permit simultaneously. When a third thread calls sem_acquire, it blocks until one of the first two calls sem_release.
Five threads compete for the semaphore. sem_count returns the number of remaining permits, which drops to 0 when both are taken. After sleeping 50 milliseconds (simulating work), each thread releases its permit and signals the WaitGroup.
This pattern is useful for rate-limiting access to external resources like database connections, file descriptors, or API endpoints where you want controlled concurrency rather than full mutual exclusion.