While watching a talk by Rob Pike I learned today something about Goroutines which surprised me: Goroutines outlive their calling function.

Said another way, if the function which created the goroutine returns, the goroutine will continue running. (main() is the one exception.) This is fantastic! πŸŽ‰

Here’s an example of this in practice.

package main

import (
    "fmt"
    "time"
)

func person(msg string) <-chan string {  // Function returns a receive-only channel
    ch := make(chan string) // Create unbuffered channel

    go func() {  // This goroutine lives on after person() returns
        for i := 0; ; i++ {
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
            ch <- fmt.Sprintf("%s %d", msg, i)
        }
    }()

    return ch
}

func main() {
    james := person("james") // Assign returned channel to variable
    sinah := person("sinah")

    for i := 0; i < 5; i++ {
        fmt.Println(<-james) // Block, waiting for value on channel
        fmt.Println(<-sinah)
    }

    fmt.Println("Done!")
}

Output:

james 0
sinah 0
james 1
sinah 1
james 2
sinah 2
james 3
sinah 3
james 4
sinah 4
Done!

Program exited.

Out of curiosity, what happens when you try to send data to a receive-only channel?

./prog.go:27:2: invalid operation: cannot send to receive-only channel james (variable of type <-chan string)

Neat!

The above code blocks here:

    for i := 0; i < 5; i++ {
        fmt.Println(<-james) // Block, waiting for value on channel
        fmt.Println(<-sinah)
    }

What is we don’t want to block? You can do something like shown below to “fan in” the incoming channels values down to a single channel.

func fanIn(input1, input2 <-chan string) <-chan string {
    ch := make(chan string)
    go func() {for {ch <- <-input1 } }()
    go func() {for {ch <- <-input2 } }()
    return ch
}

And then modify main() like so:

func main() {
    ch := fanIn(person("james"), person("sinah"))

    for i := 0; i < 5; i++ {
        fmt.Println(<-ch)
    }

    fmt.Println("Done!")
}

Then you get data from whichever channel sends data first. Neat!

sinah 0
sinah 2
james 2
sinah 5
sinah 7
Done!

Program exited.

πŸ‘‰ Other interesting things I learned from this talk:

  1. Goroutines are not threads. But thinking of them as threads is not completely wrong. But in reality, goroutines are dynamically multiplexed onto threads at runtime in order to prevent them from being blocked by other goroutines.
  2. The execution order of select is pseudorandom. Thus, the order of case statements does not matter.
  3. Goroutines are very lightweight. Some Go programs can have millions of goroutines. (I got the impression that this is an unusually high number, though.)