liveBook · Manning

I/O-bound work

As explained in 100 Go Mistakes and How to Avoid Them

An I/O-bound workload is a workload whose execution time is primarily limited by input and output operations or an external system.

I/O-bound work is limited primarily by input/output speed or an external system, such as a REST call or database query. CPU-bound work instead waits on computation, while memory-bound work waits on available memory. section 8.5

A common concurrency mistake is sizing I/O-bound goroutines like CPU-bound goroutines. CPU-bound concurrency should stay near GOMAXPROCS; I/O-bound concurrency depends on factors such as the external system. section 8.5 section 8.7

A worker-pooling implementation makes the workload and its coordination visible:

  • Read fixed-size chunks from an io.Reader and process each chunk sequentially. section 8.5
  • Create a fixed pool of goroutines that receives chunks from a shared channel. section 8.5
  • Protect the shared result with atomic updates, then close the channel and wait for every worker. section 8.5
  • Treat the pool size as an external-system decision rather than automatically setting it to GOMAXPROCS. section 8.7
func read(r io.Reader) (int, error) {
    count := 0
    for {
        b := make([]byte, 1024)
        _, err := r.Read(b)     #1
        if err != nil {
            if err == io.EOF {  #2
                break
            }
            return 0, err
        }
        count += task(b)        #3
    }
    return count, nil
}

The sequential listing shows the baseline: reading 1,024-byte chunks and applying task before accumulating the result. 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 worker-pool listing contrasts the baseline with concurrent processing through a channel, WaitGroup, and atomic counter. section 8.5

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Defines I/O-bound work, contrasts it with CPU-bound work, and works through worker-pool consequences without reducing sizing to one formula.
Examples
Uses REST calls, database queries, repeated io.Reader reads, and a ten-goroutine worker pool.
What you can do after
Diagnose workload type and choose goroutine counts using external-system constraints rather than CPU capacity alone.
How it connects
Connects I/O-bound work with concurrency, worker pooling, goroutines, channels, external systems, and CPU-bound work.

Also explained in

Compare how 5 books explain I/O-bound work →

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.