Master Go Concurrency: Goroutines, Channels, Locks, Timers and Synchronization

This comprehensive guide explains the fundamentals of concurrent programming in Go, covering the differences between parallelism and concurrency, process and thread concepts, and detailed usage of goroutines, channels, select statements, timers, mutexes, read‑write locks, wait groups, once, sync.Map, and atomic operations with practical code examples and diagrams.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Master Go Concurrency: Goroutines, Channels, Locks, Timers and Synchronization

Overview

In short, concurrency programming refers to handling multiple tasks "simultaneously" on a single processor. As hardware evolves, concurrent programs become increasingly important: web servers handle thousands of requests at once, mobile apps render UI while performing background calculations and network requests, and even traditional batch jobs hide I/O latency by using multiple cores.

Parallelism vs Concurrency

parallel

: multiple instructions execute on multiple processors at the same time. concurrency: at any moment only one instruction executes, but multiple program instructions are rapidly switched, giving the macro‑level effect of simultaneous execution through CPU time‑slice rotation.

Analogy: two queues using two coffee machines in parallel versus two queues alternating on a single coffee machine for concurrency.

Common Concurrency Techniques

Process Concurrency

A program is a compiled binary on disk that does not occupy system resources. A process is an active program that occupies resources such as CPU, memory, open files, devices, and locks.

Process states include: new, ready, running, blocked, and terminated.

Issues with process concurrency include high system overhead, limited number of processes, and the creation of orphan and zombie processes on Unix/Linux.

Thread Concurrency

Thread (light‑weight process) shares the same address space, while a process has its own address space.

Thread: smallest unit of execution.

Process: smallest unit of resource allocation, can be seen as a process with a single thread.

Thread synchronization ensures that multiple control flows operating on a shared resource execute in a defined order to avoid data races.

Mutex

Linux provides a mutex lock. Before accessing a resource, a thread attempts to lock; only after acquiring the lock can it operate, then it unlocks.

Read‑Write Lock

Allows multiple readers simultaneously but only one writer exclusively. Write mode blocks all readers; read mode allows other readers but blocks writers, with writer priority.

Coroutine Concurrency

Coroutines (light‑weight threads) can create thousands without exhausting system resources. In Go, coroutines are called goroutines.

Go Concurrency

Goroutine

A goroutine is the core of Go's concurrency model. It is lighter than a thread; thousands of goroutines can run on a few OS threads.

package main

import (
    "fmt"
    "time"
)

func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d
", i)
        time.Sleep(time.Second) // delay 1 second
    }
}

func main() {
    // create a goroutine to start another task
    go newTask()

    // loop printing
    for i := 0; i < 5; i++ {
        fmt.Printf("main goroutine: i = %d
", i)
        time.Sleep(time.Second) // delay 1 second
        i++
    }
}

If the main goroutine exits, all other goroutines are terminated.

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func(s string) {
        for i := 0; i < 2; i++ {
            fmt.Println(s)
        }
    }("world")

    for i := 0; i < 2; i++ {
        runtime.Gosched() // yield CPU
        fmt.Println("hello")
    }
}

GOMAXPROCS

Sets the maximum number of CPU cores that can execute Go code simultaneously.

package main

import (
    "fmt"
    "runtime"
)

func main() {
    n := runtime.GOMAXPROCS(2) // use 2 CPUs
    fmt.Println(n)
    for {
        go fmt.Print(0)
        fmt.Print(1)
    }
}

Channel

A channel is a core Go type that works like a pipe for communication between goroutines, simplifying synchronization without explicit locks.

package main

import "fmt"

func main() {
    c := make(chan int)
    go func() {
        defer fmt.Println("child goroutine finished")
        fmt.Println("child goroutine running…")
        c <- 666 // send to c
    }()

    num := <-c // receive from c
    fmt.Println("num = ", num)
    fmt.Println("main goroutine finished")
}

Channels can be unbuffered (synchronous) or buffered (asynchronous). Unbuffered channels block until both sender and receiver are ready; buffered channels block only when the buffer is full.

Unbuffered Channel Example

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 0) // unbuffered channel
    go func() {
        defer fmt.Println("child goroutine finished")
        for i := 0; i < 3; i++ {
            c <- i
            fmt.Printf("child running[%d]: len(c) = %d, cap(c) = %d
", i, len(c), cap(c))
        }
    }()

    time.Sleep(2 * time.Second)
    for i := 0; i < 3; i++ {
        num := <-c
        fmt.Printf("num = %d
", num)
    }
    fmt.Println("main goroutine finished")
}

Buffered Channel Example

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 3) // buffered channel with capacity 3
    fmt.Printf("len(c) = %d, cap(c) = %d
", len(c), cap(c))
    go func() {
        defer fmt.Println("child goroutine finished")
        for i := 0; i < 3; i++ {
            c <- i
            fmt.Printf("child running[%d]: len(c) = %d, cap(c) = %d
", i, len(c), cap(c))
        }
    }()

    time.Sleep(2 * time.Second)
    for i := 0; i < 3; i++ {
        num := <-c
        fmt.Printf("num = %d
", num)
    }
    fmt.Println("main goroutine finished")
}

Close Channel

When a sender knows no more values will be sent, it can close the channel so receivers can stop waiting.

Select

The select statement monitors multiple channel operations, similar to a switch but each case must be a channel send or receive.

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 6; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

Using select with a default case prevents blocking, and it can also implement timeouts.

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
            case v := <-c:
                fmt.Println(v)
            case <-time.After(5 * time.Second):
                fmt.Println("timeout")
                o <- true
                break
            }
        }
    }()
    //c <- 666 // uncomment to avoid timeout
    <-o
}

Locks

Traditional synchronization tools are provided in the sync package.

Deadlock

A deadlock occurs when two or more goroutines wait indefinitely for each other.

package main

import "fmt"

func main() {
    ch := make(chan int)
    ch <- 1 // blocked because no receiver yet
    fmt.Println("send")
    go func() {
        <-ch // never executed because main is blocked
        fmt.Println("received")
    }()
    fmt.Println("over")
}

Mutex

Only one goroutine can hold the mutex at a time, preventing data races.

package main

import (
    "fmt"
    "sync"
)

var (
    x    int64
    wg   sync.WaitGroup
    lock sync.Mutex
)

func add() {
    defer wg.Done()
    for i := 0; i < 5000; i++ {
        lock.Lock()
        x = x + 1
        lock.Unlock()
    }
}

func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

Read‑Write Mutex

Allows multiple concurrent readers but exclusive writers.

package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    x      int64
    wg     sync.WaitGroup
    rwlock sync.RWMutex
)

func write() {
    defer wg.Done()
    rwlock.Lock()
    x = x + 1
    time.Sleep(time.Millisecond * 10)
    rwlock.Unlock()
}

func read() {
    defer wg.Done()
    rwlock.RLock()
    time.Sleep(time.Millisecond)
    rwlock.RUnlock()
}

func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }
    wg.Wait()
    fmt.Println(time.Since(start))
}

WaitGroup

Synchronizes a set of goroutines, waiting until the counter drops to zero.

package main

import (
    "fmt"
    "sync"
)

func sayHello(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("Hello")
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go sayHello(&wg)
    }
    fmt.Println("main goroutine done!")
    wg.Wait()
}

Once

Ensures a function is executed only once, even under high concurrency.

package main

import "sync"

type singleton struct{}

var (
    instance *singleton
    once     sync.Once
)

func GetInstance() *singleton {
    once.Do(func() { instance = &singleton{} })
    return instance
}

sync.Map

A concurrent map that does not require explicit locking.

package main

import (
    "fmt"
    "strconv"
    "sync"
)

func main() {
    var m sync.Map
    var wg sync.WaitGroup
    for i := 0; i < 40; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            key := strconv.Itoa(n)
            m.Store(key, n)
            v, _ := m.Load(key)
            fmt.Printf("k: %v, v: %v
", key, v)
        }(i)
    }
    wg.Wait()
}

Atomic Operations

Atomic functions from sync/atomic provide lock‑free synchronization for basic types, offering higher performance than mutexes.

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

type Counter interface {
    Inc()
    Load() int64
}

type NormalCounter struct{ x int64 }
func (c *NormalCounter) Inc() { c.x++ }
func (c *NormalCounter) Load() int64 { return c.x }

type MutexCounter struct{ x int64; lock sync.Mutex }
func (c *MutexCounter) Inc() { c.lock.Lock(); c.x++; c.lock.Unlock() }
func (c *MutexCounter) Load() int64 { c.lock.Lock(); defer c.lock.Unlock(); return c.x }

type AtomicCounter struct{ x int64 }
func (c *AtomicCounter) Inc() { atomic.AddInt64(&c.x, 1) }
func (c *AtomicCounter) Load() int64 { return atomic.LoadInt64(&c.x) }

func test(c Counter) {
    var wg sync.WaitGroup
    start := time.Now()
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() { defer wg.Done(); c.Inc() }()
    }
    wg.Wait()
    fmt.Printf("Time: %v, Result: %v
", time.Since(start), c.Load())
}

func main() {
    var c1 NormalCounter   // not safe for concurrency
    test(&c1)
    var c2 MutexCounter   // safe with mutex
    test(&c2)
    var c3 AtomicCounter  // safe and faster
    test(&c3)
}
parallel vs concurrency diagram
parallel vs concurrency diagram
process states diagram
process states diagram
channel core concept diagram
channel core concept diagram
timer diagram
timer diagram
ticker diagram
ticker diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencymutexGoroutinesyncParallelismChannelatomic
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.