liveBook · Manning

Thread pool

As explained in 100 Go Mistakes and How to Avoid Them

A worker pool is a concurrency pattern that runs a fixed number of worker goroutines to process shared work or communicate through a common channel.

A worker pool runs a fixed number of goroutines that take work from a shared channel. A common mistake is choosing the pool size before asking what limits the work: CPU speed, I/O speed, or available memory. section 8.5

Processing chunks from a reader

Read 1,024-byte chunks from an io.Reader, send each chunk to the workers, and sum the results of task(b). The sequential version calls task after every read; the pooled version lets workers process chunks concurrently. section 8.5

func read(r io.Reader) (int, error) {
    var count int64
    wg := sync.WaitGroup{}
    var n = 10
 
    ch := make(chan []byte, n)        #1
    wg.Add(n)                         #2
    for i := 0; i < n; i++ {          #3
        go func() {
            defer wg.Done()           #4
            for b := range ch {       #5
                v := task(b)
                atomic.AddInt64(&count, int64(v))
            }
        }()
    }
 
    for {
        b := make([]byte, 1024)
        // Read from r to b
        ch <- b                       #6
    }
    close(ch)
    wg.Wait()                         #7
    return int(count), nil
}

The example starts ten workers, gives the task channel capacity ten, and uses a wait group and atomic additions to collect results. Its read loop is a sketch: the read operation and termination condition still need implementation before close(ch) can be reached. section 8.5

Choose the pool and its channel deliberately:

  • Classify the task first. A CPU-bound task is limited by CPU speed; an I/O-bound task waits on operations such as REST calls or database queries. GOMAXPROCS is shown as a possible pool-size value, not a universal size for every workload. section 8.5
  • Buffering the task channel to the number of workers can reduce contention while the producer publishes tasks. Do not treat an unexplained channel capacity as a tuning rule: smaller queues can increase contention, while larger ones use more memory. section 8.5 section 9.7

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Works through a Go implementation with a channel, wait group, and atomic counter; leaves the example’s read-loop details unfinished.
Examples
Contrasts sequential and pooled processing of 1,024-byte io.Reader chunks, with a ten-worker Go sketch.
What it assumes
Assumes familiarity with Go goroutines, channels, and io.Reader while defining CPU-bound and I/O-bound workloads.
What you can do after
Build it from scratch using goroutines, a shared channel, a wait group, and atomic result updates.
How it connects
Connects goroutines and channels to CPU-bound and I/O-bound workloads, channel buffering, wait groups, and atomic operations.

Also explained in

Compare how 10 books explain thread pool →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.