package main import ( "fmt" "sync" "time" ) // simulateWork simulates a task that takes some time. func simulateWork(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d starting... ", id) time.Sleep(time.Duration(id%3+1) * 100 * time.Millisecond) // Simulate varying work fmt.Printf("Worker %d finished. ", id) } func main() { maxConcurrency := 3 // Limit to 3 concurrent goroutines semaphore := make(chan struct{}, maxConcurrency) var wg sync.WaitGroup fmt.Printf("Starting %d tasks with max concurrency of %d. ", 10, maxConcurrency) for i := 1; i <= 10; i++ { semaphore <- struct{}{} // Acquire a slot in the semaphore (blocks if full) wg.Add(1) go func(taskID int) { defer func() { <-semaphore // Release the slot after the goroutine finishes }() simulateWork(taskID, &wg) }(i) } wg.Wait() // Wait for all tasks to complete fmt.Println("All tasks completed.") }