Lab 01: Goroutines & Channels

Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm golang:1.22-alpine sh

Overview

Goroutines are Go's lightweight concurrency primitives — cheaper than OS threads and scheduled by the Go runtime. Channels are typed conduits for communication between goroutines, enforcing the Go philosophy: "Do not communicate by sharing memory; instead, share memory by communicating."

Step 1: Goroutine Lifecycle

A goroutine starts with the go keyword and runs concurrently. Use a channel or sync.WaitGroup to wait for it.

// lifecycle.go
package main

import (
    "fmt"
    "time"
)

func worker(id int, done chan<- struct{}) {
    fmt.Printf("worker %d: starting\n", id)
    time.Sleep(10 * time.Millisecond)
    fmt.Printf("worker %d: done\n", id)
    done <- struct{}{}
}

func main() {
    done := make(chan struct{})
    go worker(1, done)
    <-done // block until worker finishes
    fmt.Println("main: all workers done")
}

💡 Tip: Without synchronisation, main() exits and kills all goroutines. Always wait for goroutines you care about.

Step 2: Unbuffered vs Buffered Channels

💡 Tip: Buffered channels decouple producer and consumer speed but can hide deadlocks — size them carefully.

Step 3: Channel Directions

Restricting channel direction in function signatures prevents misuse at compile time.

Step 4: Range Over Channel & close()

range over a channel reads until it is closed.

💡 Tip: Only the sender should close a channel. Closing a nil or already-closed channel panics.

Step 5: Fan-Out Pattern

Distribute work from one channel to multiple workers.

Step 6: Fan-In Pattern

Merge multiple channels into one.

Step 7: Putting It All Together

Step 8: Capstone — Pipeline

Build a three-stage pipeline: generate → square → filter (keep evens).

📸 Verified Output:

Summary

Concept
Key Points

Goroutine

go func() — lightweight, runtime-scheduled

Unbuffered channel

Synchronous handoff — both parties must be ready

Buffered channel

make(chan T, n) — async up to n items

Channel direction

chan<- T send-only, <-chan T receive-only

close() + range

Signal completion; only sender closes

Fan-out

Distribute one source to many workers

Fan-in

Merge many sources to one consumer

Pipeline

Chain stages via channels for composable data flow

Last updated