Track F · Tutorial 2

Go Concurrency, Testing & Deployment

Chapter 29 · Backend Specialization
~3 hours Advanced Goroutines · Channels · Testing · Build

Overview

This tutorial explores Go's powerful concurrency model with goroutines and channels, writing tests with the testing package, and building production binaries. You'll learn to write safe, concurrent code and deploy efficient Go applications.

Why this matters: Go's concurrency model is one of its most compelling features. Understanding goroutines and channels allows you to build high‑performance, scalable systems.

1. Goroutines & Channels

Goroutines

A goroutine is a lightweight thread managed by the Go runtime.

func sayHello() { fmt.Println("Hello from goroutine!") } func main() { go sayHello() // Starts a goroutine time.Sleep(100 * time.Millisecond) // Wait for goroutine }

Channels

Channels are the pipes that connect goroutines.

func sum(nums []int, ch chan int) { total := 0 for _, n := range nums { total += n } ch <- total } func main() { ch :=make(chan int) nums :=[]int{1, 2, 3, 4, 5} go sum(nums, ch) result :=<-ch fmt.Println(result) // Output: 15 }

2. Concurrency Patterns

Worker Pool

func worker(id int, jobs <-chan int, results chan<- int) { for job :=range jobs { time.Sleep(time.Second) results <- job * 2 } } func main() { jobs :=make(chan int, 10) results :=make(chan int, 10) for w :=1; w <=3; w++ { go worker(w, jobs, results) } for j :=1; j <=5; j++ { jobs <- j } close(jobs) for r :=1; r <=5; r++ { <-results } }

Fan‑Out / Fan‑In

Fan‑out: multiple goroutines read from the same channel. Fan‑in: multiple goroutines write to the same channel.

Select Statement

select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 :=<-ch2: fmt.Println(msg2) case <-time.After(time.Second): fmt.Println("Timeout") }
Remember: "Don't communicate by sharing memory; share memory by communicating." — Go proverb.

3. Testing with the Testing Package

Go has a built‑in testing package.

// math_test.go package main import "testing" func TestAdd(t *testing.T) { result := add(2, 3) expected := 5 if result != expected { t.Errorf("add(2,3) = %d; want %d", result, expected) } } func TestAddNegative(t *testing.T) { result := add(-1, -2) expected := -3 if result != expected { t.Errorf("add(-1,-2) = %d; want %d", result, expected) } }

Run tests: go test or go test -v

Table‑Driven Tests

func TestAddTableDriven(t *testing.T) { tests := []struct { name string a int b int want int }{ {"positive", 2, 3, 5}, {"negative", -1, -2, -3}, {"zero", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := add(tt.a, tt.b); got != tt.want { t.Errorf("add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) } }

4. Building & Deploying Go Binaries

Building

go build . # Build for current platform go build -o my-api . # Specify output name GOOS=linux GOARCH=amd64 go build . # Cross‑compile for Linux GOOS=windows GOARCH=amd64 go build . # Cross‑compile for Windows

Deployment Options

  • Binary: Upload the single binary to a server.
  • Docker: Use a minimal base image (alpine).
  • Cloud: Deploy to Google Cloud Run, AWS Lambda, or Heroku.

Dockerfile for Go

FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN go build -o my-api . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/my-api . EXPOSE 8080 CMD ["./my-api"]

Quiz

Question 1

What is a goroutine?

  • A lightweight thread managed by the Go runtime
  • A heavy system process
  • A synchronization primitive
  • A database connection pool
Show answer
A. A lightweight thread managed by the Go runtime.

Question 2

Which statement is used to send a value to a channel?

  • ch <- value
  • value := <-ch
  • ch.send(value)
  • value == ch
Show answer
A. ch <- value.

Question 3

Which command runs Go tests?

  • go test
  • go run test
  • go build test
  • go bench
Show answer
A. go test.

Exercises

Exercise 1

Write a program that starts 5 goroutines, each printing its ID. Use a sync.WaitGroup to wait for all to complete.

Sample answer
var wg sync.WaitGroup func worker(id int) { defer wg.Done() fmt.Println("Worker", id) } func main() { for i := 1; i <= 5; i++ { wg.Add(1) go worker(i) } wg.Wait() }

Exercise 2

Write a table‑driven test for a function that multiplies two integers.

Sample answer
func TestMultiply(t *testing.T) { tests := []struct { a, b, want int }{ {2, 3, 6}, {-1, 5, -5}, {0, 10, 0}, } for _, tt := range tests { got := multiply(tt.a, tt.b) if got != tt.want { t.Errorf("multiply(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } } }

Homework

Homework 1

Extend the Task Manager API from Tutorial 1 with: (1) A concurrent worker that periodically logs the number of tasks; (2) Unit tests for the handlers; (3) A Dockerfile for deployment.

Sample outline
  • Worker: Use a goroutine with a ticker to log task count every 10 seconds.
  • Tests: Write tests for GET, POST, PUT, DELETE using the testing package.
  • Dockerfile: Multi‑stage build with alpine base.
  • Build: go build -o task-api .

Mini‑Project

Concurrent URL Checker

Build a concurrent URL health checker:

  • Accepts a list of URLs from a file or API request
  • Checks each URL in parallel using goroutines
  • Returns status (200 OK, 404 Not Found, etc.)
  • Implements a timeout (5s per request)
  • Writes tests for the checker function
  • Deploy as a CLI tool or HTTP API
Sample implementation outline
  • Function: checkURL(url string) (statusCode int, err error)
  • Worker pool: Limit concurrent checks to 10 goroutines
  • Channels: URL channel for input, result channel for output
  • Timeout: Use context.WithTimeout
  • Testing: Mock HTTP responses for tests

Tutorial Summary

You explored Go's powerful concurrency model with goroutines and channels, learned to write comprehensive tests with the testing package, and mastered building and deploying Go binaries. You now have the skills to build concurrent, testable, and production‑ready Go applications.

Key takeaway: Go's concurrency primitives are simple yet powerful. Combined with its fast compilation and single‑binary deployment, Go is an excellent choice for modern cloud‑native applications.