The proof that Go channels aid concurrency

Posted on Sat 01 July 2023 in IT, programming

All the buzz about Go being the best modern language to handle concurrency... When I rewrote the Neo4j Go Driver manual I needed to get to the bottom of it. And indeed, there is some truth. After going through A Tour of Go – Concurrency, I managed to stitch together the following example, where a bunch of routines concurrently process entries from a single channel. (The sender spawns a Goroutine, but it's not a requirement – it is here, because the channel is unbuffered and the sender would thus block until a receiver would start receiving; but if you'd make it buffered, then you wouldn't need a routine.)

package main

import (
    "fmt"
    "time"
)

// Produce a channel streaming integers
func sender() <-chan int {
    out := make(chan int)
    l := []int{1,2,3,4,5,6,7,8,9,10}
    go func() {
        for _, entry := range l {
            out <- entry
        }
        close(out)
    }()
    return out
}

// Receivers listen on a channel and log on a separate one
func receiver(c <-chan int, log chan string, n int) {
    go func() {
        for entry := range c {
            log <- fmt.Sprintf("Receiver %v processed %v", n, entry)
            time.Sleep(time.Duration(n) * time.Second)
        }
    close(log)
    }()
}

func main() {
    fmt.Println("Start")
    defer timer("Main")()
    source := sender()
    log := make(chan string)
    receiver(source, log, 1)  // processes with a 1 second delay
    receiver(source, log, 2)  // processes with a 2 seconds delay
    for entry := range log {
        fmt.Println(entry)
    }
}

// Prints caller execution time
func timer(name string) func() {
    start := time.Now()
    return func() {
        fmt.Printf("%s took %v\n", name, time.Since(start))
    }
}