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:
- 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.
- The execution order of
select
is pseudorandom. Thus, the order ofcase
statements does not matter. - Goroutines are very lightweight. Some Go programs can have millions of goroutines. (I got the impression that this is an unusually high number, though.)