Explaining Go Concurrency to 12th

Understanding Go Concurrency Basics: Learning Go Programming Through a Restaurant’s Kitchen Story

Miftahul Huda
7 min readDec 30, 2024

Introduction: What is Concurrency?

Imagine walking into a busy restaurant during lunch hour. You’ll see multiple servers taking orders, chefs cooking different dishes simultaneously, and food runners delivering completed orders to tables.

This is similar to how concurrency works in programming — it’s about handling multiple tasks and making progress on them, even if they’re not literally being executed at the exact same moment. In Go, we achieve this through goroutines (the tasks) and channels (how tasks communicate).

Real-World Concurrency Example: The Restaurant Kitchen

Before diving into code, let’s understand concurrency through a familiar example: a restaurant kitchen. In a busy restaurant, you have:

  • Multiple chefs working on different dishes (like goroutines)
  • Order tickets being passed between stations (like channels)
  • Expeditors coordinating orders (like select statements)
  • Kitchen stations with limited counter space (like buffered channels)

This setup allows the kitchen to handle multiple orders simultaneously while maintaining organization and efficiency.

Visualize of workflow

Restaurant System Overview

  • Shows the overall structure with channels and goroutines
  • Visualizes how orders flow through the system
  • Demonstrates parallel processing in the kitchen
Restaurant System Overview (Mermaidchart)

Order Processing Flow

  • Details the sequence of events for each order
  • Shows communication between different components
  • Illustrates concurrent processing by chefs
Order Processing Flow (Mermaidchart)

Error Handling and Timeout Flow

  • Demonstrates the state transitions
  • Shows error and timeout handling paths
  • Illustrates resource cleanup
Error Handling and Timeout Flow (Mermaidchart)

Basic Concurrency Implementation

Let’s start with a simple restaurant order system:

package main

import (
"fmt"
"time"
)

type Order struct {
ID int
DishName string
TableNum int
}

func prepareOrder(order Order, done chan<- bool) {
fmt.Printf("Starting to prepare %s for table %d\n", order.DishName, order.TableNum)
// Simulate cooking time
time.Sleep(2 * time.Second)
fmt.Printf("Finished %s for table %d\n", order.DishName, order.TableNum)
done <- true
}

func main() {
done := make(chan bool)
orders := []Order{
{1, "Pasta Carbonara", 4},
{2, "Grilled Salmon", 2},
{3, "Caesar Salad", 1},
}

// Start cooking each order
for _, order := range orders {
go prepareOrder(order, done)
}

// Wait for all orders to complete
for range orders {
<-done
}
}

In this example, each order is processed independently using goroutines, similar to how different chefs can work on different dishes simultaneously. The done channel acts like a notification system - when a dish is completed, the chef (goroutine) signals completion through this channel. The main function waits for all orders to be completed before finishing, just like how a restaurant doesn't close until all orders are served.

Deep Dive to big part

1. Setting Up Our Restaurant Types

Let’s start by defining our basic data structures:

package main

import (
"fmt"
"time"
)

// Order represents a customer's order
type Order struct {
ID int
DishName string
TableNumber int
SpecialNotes string
OrderTime time.Time
}

// Chef represents a kitchen worker
type Chef struct {
ID int
Speciality string
Experience int
}

// Restaurant represents our restaurant system
type Restaurant struct {
Name string
Capacity int
Orders chan Order
Done chan bool
}

2. Basic Concurrent Order Processing

func (r *Restaurant) Initialize() {
// Initialize channels with buffer size based on capacity
r.Orders = make(chan Order, r.Capacity)
r.Done = make(chan bool, r.Capacity)

fmt.Printf("Restaurant %s initialized with capacity of %d orders\n",
r.Name, r.Capacity)
}

func (chef Chef) PrepareOrder(order Order, done chan<- bool) {
// Log start of preparation
startTime := time.Now()
fmt.Printf("Chef %d starting to prepare %s for table %d\n",
chef.ID, order.DishName, order.TableNumber)

// Simulate cooking time based on chef's experience
cookingTime := 2 * time.Second
if chef.Experience > 5 {
cookingTime = time.Second
}
time.Sleep(cookingTime)

// Calculate preparation time
prepTime := time.Since(startTime)

fmt.Printf("Chef %d completed %s for table %d in %v\n",
chef.ID, order.DishName, order.TableNumber, prepTime)

// Signal completion
done <- true
}

Channel Initialization

  • We use buffered channels (make(chan Order, r.Capacity)) to prevent blocking
  • The buffer size matches restaurant capacity, simulating limited kitchen space
  • Example: If capacity is 5, up to 5 orders can be in processing before blocking

Order Preparation Flow

  • Each order gets its own goroutine (like assigning a chef to a specific order)
  • The chef’s experience affects cooking time (business logic example)
  • We track and log preparation time for monitoring efficiency

3. Order Management System

func (r *Restaurant) ManageOrders(orders []Order) {
fmt.Printf("Starting order management for %d orders\n", len(orders))

// Create a team of chefs
chefs := []Chef{
{ID: 1, Speciality: "Main Course", Experience: 7},
{ID: 2, Speciality: "Main Course", Experience: 3},
{ID: 3, Speciality: "Appetizers", Experience: 5},
}

// Track time for full order processing
startTime := time.Now()

// Start processing each order
for i, order := range orders {
// Select available chef (round-robin for simplicity)
chef := chefs[i%len(chefs)]

// Start cooking in a new goroutine
go chef.PrepareOrder(order, r.Done)

fmt.Printf("Assigned order %d (%s) to Chef %d\n",
order.ID, order.DishName, chef.ID)
}

// Wait for all orders to complete
for i := 0; i < len(orders); i++ {
<-r.Done
fmt.Printf("Completed order %d of %d\n", i+1, len(orders))
}

totalTime := time.Since(startTime)
fmt.Printf("All orders completed in %v\n", totalTime)
}

Chef Assignment

  • We use round-robin assignment for simplicity
  • In real systems, you might want to consider:
  • Chef speciality matching dish type
  • Current chef workload
  • Order priority or complexity

Concurrent Processing

  • Each PrepareOrder runs in its own goroutine
  • This allows multiple orders to be prepared simultaneously
  • The system naturally handles parallel preparation

Completion Tracking

  • The Done channel acts as our order completion system
  • We wait for exactly the number of orders we started
  • This prevents the program from ending prematurely

4. Adding Error Handling and Timeout

func (chef Chef) PrepareOrderWithTimeout(order Order) error {
done := make(chan bool)
errors := make(chan error)

go func() {
defer close(done)
// Simulate potential issues during preparation
if rand.Float32() < 0.1 { // 10% chance of error
errors <- fmt.Errorf("chef %d: error preparing %s: kitchen equipment failure",
chef.ID, order.DishName)
return
}

time.Sleep(2 * time.Second) // Normal preparation time
done <- true
}()

// Set maximum preparation time
select {
case <-done:
return nil
case err := <-errors:
return err
case <-time.After(5 * time.Second):
return fmt.Errorf("order %d timed out: preparation took too long",
order.ID)
}
}

Timeout Implementation

  • Uses select statement with time.After
  • Prevents orders from being stuck indefinitely
  • Simulates real kitchen timing constraints

Error Scenarios

  • Random errors simulate real kitchen issues
  • Separate channel for error reporting
  • Clean error handling with proper return values

5. Complete Working Example

func main() {
// Initialize restaurant
restaurant := &Restaurant{
Name: "Gopher's Diner",
Capacity: 5,
}
restaurant.Initialize()

// Create sample orders
orders := []Order{
{ID: 1, DishName: "Pasta Carbonara", TableNumber: 1,
OrderTime: time.Now()},
{ID: 2, DishName: "Grilled Salmon", TableNumber: 2,
OrderTime: time.Now()},
{ID: 3, DishName: "Caesar Salad", TableNumber: 3,
OrderTime: time.Now()},
{ID: 4, DishName: "Steak", TableNumber: 4,
OrderTime: time.Now()},
{ID: 5, DishName: "Mushroom Risotto", TableNumber: 5,
OrderTime: time.Now()},
}

// Process all orders
restaurant.ManageOrders(orders)
}

Program Flow

  • Restaurant initialization
  • Order creation
  • Concurrent order processing
  • Completion handling
  • Final cleanup

Output Analysis You’ll see output like

Restaurant Gopher's Diner initialized with capacity of 5 orders
Assigned order 1 (Pasta Carbonara) to Chef 1
Chef 1 starting to prepare Pasta Carbonara for table 1
Assigned order 2 (Grilled Salmon) to Chef 2
Chef 2 starting to prepare Grilled Salmon for table 2
...
Chef 1 completed Pasta Carbonara for table 1 in 1.002s
Completed order 1 of 5
...
All orders completed in 2.005s

Best Practices Summary

Resource Management

  • Always clean up resources using defer statements
  • Use context for cancellation and timeout management
  • Close channels from the sender side only

Error Handling

  • Implement timeout mechanisms for all blocking operations
  • Use buffered channels when appropriate
  • Handle all potential error cases

Performance

  • Use worker pools for managing concurrent operations
  • Implement proper load balancing
  • Monitor and manage system resources

Conclusion

Concurrent programming in Go mirrors many real-world scenarios we encounter daily. By understanding these patterns and practicing with realistic examples, you can build robust concurrent systems that handle multiple tasks efficiently.

Remember that like a well-run restaurant kitchen, good concurrent programs require careful planning, proper resource management, and clear communication patterns.

The key to mastering concurrency is to start with simple patterns and gradually build up to more complex systems as your understanding grows. Always keep in mind the potential pitfalls and follow established best practices to create reliable and efficient concurrent programs.

If you’re interested in diving deeper into system design and backend development, be sure to follow me for more insights, tips, and practical examples. Together, we can explore the intricacies of creating efficient systems, optimizing database performance, and mastering the tools that drive modern applications. Join me on this journey to enhance your skills and stay updated on the latest trends in the tech world! 🚀

Read the design system in bahasa on iniakunhuda.com

--

--

Miftahul Huda
Miftahul Huda

Written by Miftahul Huda

Backend Developer | Exploring Software Architecture

No responses yet