I've commented several times in various forums that basically every time I've seen the “worker goroutine” pattern in Go, there has turned out to be a cleaner implementation using semaphores. This change adds a simple such example. (For more complex usage, I would generally pair the semaphore with an errgroup.Group.) Change-Id: Ibf69ee761d14ba59c1acc6a2d595b4fcf0d8f6d6 Reviewed-on: https://go-review.googlesource.com/75170 Reviewed-by: Ross Light <light@google.com>
		
			
				
	
	
		
			84 lines
		
	
	
	
		
			2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			84 lines
		
	
	
	
		
			2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2017 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package semaphore_test
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"runtime"
 | |
| 
 | |
| 	"golang.org/x/sync/semaphore"
 | |
| )
 | |
| 
 | |
| // Example_workerPool demonstrates how to use a semaphore to limit the number of
 | |
| // goroutines working on parallel tasks.
 | |
| //
 | |
| // This use of a semaphore mimics a typical “worker pool” pattern, but without
 | |
| // the need to explicitly shut down idle workers when the work is done.
 | |
| func Example_workerPool() {
 | |
| 	ctx := context.TODO()
 | |
| 
 | |
| 	var (
 | |
| 		maxWorkers = runtime.GOMAXPROCS(0)
 | |
| 		sem        = semaphore.NewWeighted(int64(maxWorkers))
 | |
| 		out        = make([]int, 32)
 | |
| 	)
 | |
| 
 | |
| 	// Compute the output using up to maxWorkers goroutines at a time.
 | |
| 	for i := range out {
 | |
| 		// When maxWorkers goroutines are in flight, Acquire blocks until one of the
 | |
| 		// workers finishes.
 | |
| 		if err := sem.Acquire(ctx, 1); err != nil {
 | |
| 			log.Printf("Failed to acquire semaphore: %v", err)
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		go func(i int) {
 | |
| 			defer sem.Release(1)
 | |
| 			out[i] = collatzSteps(i + 1)
 | |
| 		}(i)
 | |
| 	}
 | |
| 
 | |
| 	// Acquire all of the tokens to wait for any remaining workers to finish.
 | |
| 	//
 | |
| 	// If you are already waiting for the workers by some other means (such as an
 | |
| 	// errgroup.Group), you can omit this final Acquire call.
 | |
| 	if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil {
 | |
| 		log.Printf("Failed to acquire semaphore: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	fmt.Println(out)
 | |
| 
 | |
| 	// Output:
 | |
| 	// [0 1 7 2 5 8 16 3 19 6 14 9 9 17 17 4 12 20 20 7 7 15 15 10 23 10 111 18 18 18 106 5]
 | |
| }
 | |
| 
 | |
| // collatzSteps computes the number of steps to reach 1 under the Collatz
 | |
| // conjecture. (See https://en.wikipedia.org/wiki/Collatz_conjecture.)
 | |
| func collatzSteps(n int) (steps int) {
 | |
| 	if n <= 0 {
 | |
| 		panic("nonpositive input")
 | |
| 	}
 | |
| 
 | |
| 	for ; n > 1; steps++ {
 | |
| 		if steps < 0 {
 | |
| 			panic("too many steps")
 | |
| 		}
 | |
| 
 | |
| 		if n%2 == 0 {
 | |
| 			n /= 2
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		const maxInt = int(^uint(0) >> 1)
 | |
| 		if n > (maxInt-1)/3 {
 | |
| 			panic("overflow")
 | |
| 		}
 | |
| 		n = 3*n + 1
 | |
| 	}
 | |
| 
 | |
| 	return steps
 | |
| }
 |