A lightweight Go package that provides a simple, efficient implementation of a counting semaphore with synchronization capabilities.
The semaphore package offers a straightforward way to limit concurrent operations in Go applications. It combines a channel-based semaphore with a wait group to both limit concurrency and provide synchronization of concurrent tasks.
go get github.com/nawivee/semaphorepackage main
import (
"fmt"
"time"
"github.com/nawivee/semaphore"
)
func main() {
// Create a semaphore with a concurrency limit of 3
sem := semaphore.NewSemaphore(3)
// Launch 10 goroutines
for i := 0; i < 10; i++ {
// Acquire a token from the semaphore
sem.Acquire()
go func(id int) {
// Ensure token is released when done
defer sem.Release()
// Simulate work
fmt.Printf("Worker %d starting\n", id)
time.Sleep(2 * time.Second)
fmt.Printf("Worker %d done\n", id)
}(i)
}
// Wait for all goroutines to complete
sem.Wait()
fmt.Println("All workers have completed")
}func fetchURLs(urls []string, concurrencyLimit uint) []string {
sem := semaphore.NewSemaphore(concurrencyLimit)
results := make([]string, len(urls))
for i, url := range urls {
sem.Acquire()
go func(index int, url string) {
defer sem.Release()
// Fetch the URL
resp, err := http.Get(url)
if err != nil {
results[index] = fmt.Sprintf("Error: %v", err)
return
}
defer resp.Body.Close()
// Process the response
body, _ := io.ReadAll(resp.Body)
results[index] = string(body)
}(i, url)
}
// Wait for all requests to complete
sem.Wait()
return results
}Run the tests using:
go test -v ./...- Always pair
Acquire()withRelease()calls, preferably usingdeferto ensure tokens are released even if a panic occurs. - Use a concurrency limit appropriate for your resources. For CPU-bound tasks, consider using
runtime.NumCPU()as a guideline. - For I/O-bound tasks like network requests, higher concurrency limits may be appropriate.
All methods in this package are thread-safe and can be safely called from multiple goroutines.
MIT