logo

This short course (recently refreshed for Go 1.26) aims at demystifying Go’s main concurrency features: goroutines, channels, mutexes, select statements, and contexts. Through a series of practical exercises, you’ll learn how to use concurrency to your advantage and avoid its many pitfalls.

Duration

1 day (7 hours)

Prerequisites

  • Fluency in English
  • Experience with the basics of Go but not necessarily with its concurrency features
  • A basic knowledge of Git
  • A personal computer (preferably running macOS or Linux) on which the following tools have been installed (ahead of the course):
  • A stable Internet connection

Outline

  • Presentation of the namecheck project
    • namecheck: a CLI tool for checking the validity and availability of usernames on social media (GitHub, Reddit, etc.)
    • Motivations for developing such a tool
  • Introduction to concurrency
    • Concurrency: the art of composing a program in order to delineate independent tasks
    • Concurrency vs. parallelism: close but distinct
    • Concurrency enables parallelism and promises free performance gains as the number of cores increases
    • Amdahl’s law and Gunther’s law: parallelism’s diminishing returns
    • Go’s concurrency trifecta: goroutines, channels, et select statements
  • Goroutines
    • Goroutines: featherweight threads
    • The Go scheduler
    • Starting goroutines with the go keyword
    • The golden rule: don’t start a goroutine if you can’t explain when it will terminate
    • The main function doesn’t wait for other goroutines to terminate
    • Wait groups
    • Synchronization bugs and race conditions
    • Concurrency safety
    • Data races
    • Race detector
  • Channels
    • Shared-memory concurrency vs. message-passing concurrency
    • Share memory by communicating
    • Channels: the preferred mechanism for communicating between goroutines
    • Naming conventions
    • Channels are values just like any other
    • The zero value of channel types
    • Initializing a channel via the make builtin
    • Capacity of a channel
    • Channels are FIFO
    • On the importance of choosing a channel’s capacity judiciously
    • Sending a value to a channel
    • Receiving a value from a channel
    • Lifting ambiguity at receiption with the comma-ok idiom
    • Deadlock
    • Goroutine leak
    • Closing a channel
    • Good and bad reasons to close a channel
    • Ranging over a channel
    • Directional channels
  • HTTP server
    • Introduction to the net/http package
    • A simple “Hello, World!” server
    • What about Web frameworks?
    • Beware: handlers are invoked concurrently
  • A short detour in the land of shared-memory concurrency
    • Communicating by sharing memory
    • Mutexes and critical sections
    • The sync.Mutex type
  • Select statements
    • Multiplexing channel communications
    • The select keyword
    • Event loop: a select statement nested in a loop
  • Canceling goroutines
    • The need to force goroutines to terminate
    • Emit and detect a cancellation signal: the basic technique
    • The context.Context type
    • Emit and detect a cancellation signal: the modern approach
    • Cancelable channel send
    • context.Background() and context.TODO()
    • Deriving contexts
    • Cancellation signals propagate down the tree of contexts
    • Embrace “cancel culture”: design most of your functions as cancelable
    • Contexts on the client side and on the (Web) server side
  • Learning resources

Labs:

  • Refactor the namecheck project with concurrency in mind
  • Analysis and resolution of a simple deadlock
  • Analysis and resolution of a simple goroutine leak
  • Aggregate the results from multiple goroutines thanks to a channel
  • Turn the CLI tool into a Web server
  • Configure your server for CORS thanks to a third-party library
  • Use a mutex to guard a shared map storing statistics about the service in memory
  • Analysis and resolution of a goroutine leak thanks to contexts
  • Use contexts to cancel tasks whose results are no longer necessary