Goroutines
So far, programs have done one thing at a time: finish one, then start the next. Download three files and they come down one after another:
1package main2 3import "fmt"4 5func download(file string) {6 fmt.Println("Downloaded", file)7}8 9func main() {10 download("photo.jpg")11 download("music.mp3")12 download("video.mp4")13 fmt.Println("All done")14}
Downloaded photo.jpg Downloaded music.mp3 Downloaded video.mp4 All done
A real download waits on the network: a few seconds per file, three times that for three files. The three jobs don't depend on each other, so they could happen at the same time. Go's way to do that is the goroutine: think of it as another line of work inside your program. main is one; you can start many more.
go: start a goroutine
Put go in front of a function call and that call runs in a new goroutine. main doesn't wait for it; it carries straight on:
1package main2 3import "fmt"4 5func download(file string) {6 fmt.Println("Downloaded", file)7}8 9func main() {10 fmt.Println("Starting the download")11 go download("photo.jpg")12}
Starting the download
Only "Starting the download" shows; the line inside download never appears. Press Step and watch: after the go line, a new lane appears on the right. That's the new goroutine, marked "ready". The main lane is marked "running". Then main reaches its closing }, the program ends, and the new goroutine never took a single step.
When main ends, the program ends
main runs in a goroutine too: the goroutine 1 [running]: you saw in earlier panics is main. When main returns, the whole program ends, and every other goroutine ends with it, however far it got. Go doesn't wait for them.
Note: go only says "this can start now". It makes no promise about when it runs. On a real computer, several CPUs work at once, and now and then a new goroutine does get a few steps in before main ends. So don't count on it never running, and don't count on it finishing: if you need to wait for it, wait for it on purpose.
Sleep for a while?
The first idea: make main end a little later. time.Sleep(duration) pauses the current goroutine. time.Second is one second, and 100 * time.Millisecond is a tenth of a second:
1package main2 3import (4 "fmt"5 "time"6)7 8func download(file string) {9 fmt.Println("Downloaded", file)10}11 12func main() {13 fmt.Println("Starting the download")14 go download("photo.jpg")15 time.Sleep(time.Second)16 fmt.Println("main is done")17}
Starting the download Downloaded photo.jpg main is done
Now all three lines show. Press Step: at time.Sleep the main lane turns to "sleeping", so the new goroutine finally gets its turn and runs download. Then main wakes up and shows the last line.
Note: On this page time.Sleep doesn't really wait: when every goroutine is paused, the clock jumps straight to the next wake-up time. On your own computer it really waits a second.
The trouble is: how long is long enough? You can't know how long a download takes. Here each file takes 0.3 seconds, and main waits 0.1:
What does this show?
1package main2 3import (4 "fmt"5 "time"6)7 8func download(file string) {9 time.Sleep(300 * time.Millisecond)10 fmt.Println("Downloaded", file)11}12 13func main() {14 for _, file := range []string{"a.txt", "b.txt", "c.txt"} {15 go download(file)16 }17 time.Sleep(100 * time.Millisecond)18 fmt.Println("Giving up")19}
All three goroutines start, but each sleeps for 0.3 seconds. main wakes after 0.1, shows "Giving up" and ends. When main ends, the three sleeping goroutines end with it, so no "Downloaded" line ever shows.
Wait too little and the results aren't there; wait too long and the program sits idle for nothing. Waiting with Sleep is guessing. The channels in the next lesson let main wait exactly until a goroutine is done, no more and no less.
Several goroutines at once
After go you can also put an anonymous function (lesson 16), called straight away with () after its body. Here each number gets its own goroutine, which works out its square and writes it into its own slot in squares:
1package main2 3import (4 "fmt"5 "time"6)7 8func main() {9 nums := []int{3, 5, 8, 10}10 squares := make([]int, len(nums))11 for i, n := range nums {12 go func() {13 squares[i] = n * n14 }()15 }16 time.Sleep(100 * time.Millisecond)17 fmt.Println(squares)18}
[9 25 64 100]
Press Step and look at the right once the loop is done: five lanes, main and four goroutines waiting their turn. When main sleeps, they run one after another and the slots of squares fill up. Each round of the loop has its own new i and n (since Go 1.22), so each goroutine gets the values of its own round.
Still, Sleep isn't real synchronization: main just naps for a while and never checks that they're done. Run this on your own computer with go run -race (lesson 22) and it's reported as a data race. From the next lesson on, we stop waiting with Sleep.
Why not let each goroutine show its own result? Because the order isn't fixed:
1for _, n := range nums {2 go func() {3 fmt.Println(n * n)4 }()5}
On a real computer, goroutines really do run at the same time, and who shows first can change from run to run: 9 25 64 100 this time, maybe 64 9 100 25 the next. The engine on this page runs one goroutine at a time (until it has to stop), so it's the same every time. Don't let that fool you. The examples in this course never depend on the order: each goroutine writes its own slot, and main shows them all at the end.
go takes a call
The return value of a function started with go goes nowhere; it's thrown away. So you can't get a result like this:
1package main2 3import "fmt"4 5func square(n int) int {6 return n * n7}8 9func main() {10 result := go square(7)11 fmt.Println(result)12}
# command-line-arguments
./prog.go:10:12: syntax error: unexpected keyword go, expected expressiongo square(7) is a statement, not a value, so putting it after := is a syntax error. Another common mistake is forgetting the () after an anonymous function:
1package main2 3import "fmt"4 5func main() {6 go func() {7 fmt.Println("Downloaded")8 }9}
# command-line-arguments
./prog.go:6:5: expression in go must be function callexpression in go must be function call: go needs a call, and this is just a function that nobody calls. Add () after the } and it works. To get a result back from a goroutine, write it into a variable as in the last section; the better way is a channel, in the next lesson.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it. There's no proper way to wait yet, so these use time.Sleep for now. It isn't real synchronization, and go run -race reports these programs as data races; from the next lesson on, we stop waiting with Sleep.
Fix it: the totals are still 0
✓showTotals starts one goroutine per order to work out its total, but they all show as 0. Fix it so it shows each order's real total. The program does two batches of orders.
Totals [50 100] Totals [10 40 20]
1package main2 3import "fmt"4 5func sum(prices []int) int {6 total := 07 for _, p := range prices {8 total += p9 }10 return total11}12 13func showTotals(orders [][]int) {14 totals := make([]int, len(orders))15 for i, prices := range orders {16 go func() {17 totals[i] = sum(prices)18 }()19 }20 fmt.Println("Totals", totals)21}22 23func main() {24 showTotals([][]int{{12, 30, 8}, {99, 1}})25 showTotals([][]int{{5, 5}, {40}, {7, 3, 10}})26}
A crew of workers adding up squares
✓Finish sumOfSquares: start one goroutine per number that writes its square into its slot in squares; after waiting a little, add up squares and return the total. The program shows the sum of squares of two lists.
14 41
1package main2 3import (4 "fmt"5 "time"6)7 8func sumOfSquares(nums []int) int {9 squares := make([]int, len(nums))10 // start the goroutines here11 time.Sleep(100 * time.Millisecond)12 total := 013 // add up squares here14 return total15}16 17func main() {18 fmt.Println(sumOfSquares([]int{1, 2, 3}))19 fmt.Println(sumOfSquares([]int{4, 5}))20}
Fix it: go gives nothing back
✓countBoth wants to count the words in two sentences at the same time, but it doesn't compile. Change it so two goroutines count one sentence each and write the results into a and b, then wait a little and return them. The program counts two pairs of sentences.
4 6 1 4
1package main2 3import (4 "fmt"5 "strings"6)7 8func countWords(s string) int {9 return len(strings.Fields(s))10}11 12func countBoth(s1, s2 string) (int, int) {13 a := go countWords(s1)14 b := go countWords(s2)15 return a, b16}17 18func main() {19 fmt.Println(countBoth("the order has shipped", "your download will start in seconds"))20 fmt.Println(countBoth("done", "please try again later"))21}
What you learned
go f(x)runs that call in a new goroutine;maindoesn't wait and carries on.mainis a goroutine too; whenmainends, the program ends, and every other goroutine with it.- Step shows one lane per goroutine: "running", "ready", "sleeping".
go func() { … }(): an anonymous function needs()after it; agostatement has no value, so you can't get a return value from it.- Waiting with
time.Sleepis guessing: too short and the result isn't there, too long and you wait for nothing. Next lesson, channels wait exactly until the work is done. - The order of several goroutines isn't fixed: let each write its own slot, wait until they're all done (next lesson), and then show them together.