Use a buffered channel as a semaphore to limit the number of concurrent goroutines. Create a channel with the desired capacity, send a token before starting work, and receive a token when finished.
sem := make(chan struct{}, 10) // Limit to 10 concurrent goroutines
for _, item := range items {
sem <- struct{}{} // Acquire token
go func(i interface{}) {
defer func() { <-sem }() // Release token
process(i)
}(item)
}
// Wait for all goroutines to finish
for range items {
<-sem
}