advanced25 min

Goroutines and Channels

Go's lightweight concurrency primitives, and how channels coordinate them safely.

What you'll learn

  • Start a concurrent goroutine with the `go` keyword
  • Use a channel to safely communicate a value between goroutines
  • Explain why Go's motto is 'share memory by communicating' rather than locking shared memory directly

Explanation

A goroutine is a lightweight, independently-scheduled function execution -- you start one just by prefixing a function call with go: go doWork(). Goroutines are far cheaper than OS threads (a Go program can comfortably run tens of thousands of them), managed by Go's own runtime scheduler.

Goroutines alone are dangerous without coordination -- reading and writing the same variable from multiple goroutines at once is a data race. Go's answer is the channel: a typed conduit for sending values between goroutines safely, created with make(chan int). One goroutine sends with ch <- value, another receives with value := <-ch -- both operations block until the other side is ready, which naturally synchronizes the two goroutines.

This reflects Go's concurrency motto: "Don't communicate by sharing memory; share memory by communicating." Instead of multiple goroutines directly touching the same variable behind a lock, they pass values to each other through channels, so only one goroutine ever "owns" a piece of data at a time.

This walkthrough starts from a plain sequential version, then adds a goroutine and a channel step by step so you can see exactly what each addition changes.

Guided lab

Guided edit: From sequential to a goroutine with a channel

GoNot executed
This lab does not run in your browser or on VisaSparkSchools's servers. Read the code, follow each edit step by step and see the expected output after every change.

Follow each step to see how adding a goroutine and a channel changes this program.

Step 1 of 2

Start with a plain sequential function call -- no concurrency yet.

package main

import "fmt"

func square(n int) int {
	return n * n
}

func main() {
	result := square(6)
	fmt.Println("Result:", result)
}

Stuck? Get a hint.

Common mistakes

  • Starting a goroutine and letting `main` return before it finishes -- when `main` returns, the whole program exits immediately, goroutines and all.
  • Reading/writing a shared variable from multiple goroutines directly without a channel or lock, causing a data race.
  • Forgetting that both a channel send and receive block until the other side is ready (for an unbuffered channel), which can deadlock a program with no one else to receive/send.

Knowledge check

Knowledge check

1. What keyword starts a new goroutine?
2. What is Go's concurrency motto?
3. What happens to running goroutines when `main` returns?

Takeaway

Use a channel to hand a value from one goroutine to another safely -- and remember that `main` returning ends the whole program, unfinished goroutines included.

Summary

Goroutines are lightweight concurrent function calls started with `go`; channels safely pass values between them, embodying Go's 'share memory by communicating' philosophy.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.