BuildBot

Go by Contrast

Goroutines & channels

Lesson 4 of 5

What you'll learn

  • Understand goroutines as cheap, runtime-scheduled concurrency
  • See how channels pass values safely between goroutines
  • Contrast with JS's single-threaded event loop and Promises

JavaScript is single-threaded: concurrency means an event loop interleaving callbacks and awaited Promises. Go is genuinely concurrent. A goroutine is a function running independently, scheduled by the Go runtime onto real OS threads. They're cheap — you can launch thousands — and you start one by prefixing a call with go.

go doWork()        // runs concurrently; the caller keeps going
fmt.Println("kept going")

But how do goroutines communicate without data races? With channels — typed pipes you send values into and receive values out of.

ch := make(chan string)

go func() {
    ch <- "result"      // send (blocks until someone receives)
}()

msg := <-ch              // receive (blocks until someone sends)
fmt.Println(msg)         // "result"

The closest TS analogy is a Promise plus an async queue, but the model is different — channels are synchronous handoffs by default:

async function doWork(): Promise<string> {
  return "result";
}

const msg = await doWork();
console.log(msg);

Share memory by communicating

Go's mantra is "Do not communicate by sharing memory; share memory by communicating." Instead of multiple goroutines locking a shared variable, one goroutine owns the data and others send it messages over a channel. Ordering is preserved: values come out of a channel in the order they went in.

A producer/consumer loop is the canonical shape — producers ch <- v and a consumer ranges over the channel until it's closed:

for v := range ch {   // receives until ch is closed
    process(v)
}

Channels block

An unbuffered channel blocks the sender until a receiver is ready (and vice versa). That's a feature — it synchronizes goroutines — but forget to receive and the sender hangs forever. This is Go's most common beginner deadlock.

The challenge is a JS model of a channel: a queue where producers enqueue values and a consumer drains them in order. Real Go would block and schedule across goroutines; here we model just the ordered handoff.

Channel handoff (JS model)

Run it. This models producers sending into a channel and a consumer receiving the values in order.

Loading editor…
Knowledge check

What happens when a goroutine sends on an unbuffered channel with no receiver ready?

Next: the data structures and standard library that make Go productive out of the box.

Saved on this device. Sign in to sync your progress everywhere.