Mastering Go Concurrency: Goroutines, Channels, and Synchronization Explained
This article provides a comprehensive guide to Go's concurrency model, covering goroutine creation, the scheduler, communication via channels, handling race conditions, and synchronization techniques using atomic operations and mutexes, complete with practical code examples and visual diagrams.
Preface
Studying GO, so this article was written.
This blog post is a reading note of "Go Language in Practice".
The author wrote it so well that the entire chapter is quoted.
Help correct any misunderstandings.
For everyone, the true responsibility is to find oneself and stay true to it; all other paths are incomplete, escapist, or fearful.
Content Summary:
Concurrency means that goroutine runs independently.
Use the go keyword to create a goroutine to run a function.
A goroutine runs on a logical processor, which has its own system thread and run queue.
A race condition occurs when two or more goroutines try to access the same resource simultaneously.
Atomic functions and mutexes provide ways to prevent race conditions.
Channels provide a simple method for two goroutine to share data.
Unbuffered channels guarantee simultaneous data exchange, while buffered channels do not.
Concurrency
In coding, parallel execution of multiple tasks brings great benefits. For example, a web service needs to receive multiple data requests on independent sockets simultaneously; each socket request is independent and can be processed independently, improving performance.
Considering this, Go's syntax and runtime directly embed concurrency support.
In Go, concurrency means the ability for a function to run independently of other functions. When a function is created as a goroutine, Go treats it as an independent work unit that is scheduled onto an available logical processor.
The Go runtime scheduler is a complex software that manages all created goroutine and allocates execution time. It runs on top of the operating system, binding OS threads to the language runtime's logical processors, and runs goroutine on those logical processors.
At any given time, the scheduler fully controls which goroutine runs on which logical processor.
Go's concurrency synchronization model comes from the Communicating Sequential Processes (CSP) paradigm.
CSP is a message‑passing model where goroutine exchange data via channels instead of locking data for synchronized access.
Concurrency vs Parallelism
The OS schedules threads on physical processors, while Go's runtime schedules goroutine on logical processors. Each logical processor is bound to a single OS thread.
Since Go 1.5, the runtime defaults to assigning one logical processor per physical processor; earlier versions assigned only one logical processor to the whole program.
When a goroutine is created, it is placed in the scheduler's global run queue, then assigned to a logical processor's local run queue for execution.
If a running goroutine performs a blocking system call (e.g., opening a file), the thread and goroutine detach from the logical processor, the thread blocks, and the scheduler creates a new thread for the logical processor to keep other goroutines running.
In many cases, concurrency yields better results than parallelism because hardware resources are limited; Go's design philosophy is to do more with fewer resources.
To achieve true parallelism, multiple logical processors must be used, and the program must run on a machine with multiple physical CPUs.
Race Condition
If two or more goroutine access a shared resource without synchronization, attempting simultaneous reads and writes, a race condition occurs.
Access to a shared resource must be atomic—only one goroutine may read or write at a time.
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int
wg sync.WaitGroup
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println("Final Counter:", counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
value := counter
runtime.Gosched()
value++
counter = value
}
}This code demonstrates how race conditions can cause each goroutine to work on stale copies of counter, leading to lost updates.
Go provides a race detector; compile with go build -race to find such issues.
Locking Shared Resources
Go offers traditional synchronization mechanisms: atomic functions and sync.Mutex. The atomic package provides lock‑free operations like AddInt64, LoadInt64, and StoreInt64. The Mutex creates a critical section where only one goroutine can execute at a time.
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int
wg sync.WaitGroup
mutex sync.Mutex
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Printf("Final Counter: %d
", counter)
}
func incCounter(id int) {
defer wg.Done()
for count := 0; count < 2; count++ {
mutex.Lock()
value := counter
runtime.Gosched()
value++
counter = value
mutex.Unlock()
}
}Channels
Channels enable safe data exchange and synchronization between goroutines. Declare a channel with make(chan Type) for unbuffered or make(chan Type, capacity) for buffered channels.
// Unbuffered integer channel
unbuffered := make(chan int)
// Buffered string channel with capacity 10
buffered := make(chan string, 10)Send with chan <- value; receive with value := <-chan. Unbuffered channels block until both sender and receiver are ready, providing synchronous communication. Buffered channels allow sending up to the buffer size without blocking.
Unbuffered Channels
Unbuffered channels have no capacity; a send blocks until a receiver is ready, and vice versa. This guarantees synchronous data exchange.
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func init() { rand.Seed(time.Now().UnixNano()) }
func main() {
court := make(chan int) // unbuffered channel
wg.Add(2)
go player("Nadal", court)
go player("Djokovic", court)
court <- 1 // serve the ball
wg.Wait()
}
func player(name string, court chan int) {
defer wg.Done()
for {
ball, ok := <-court
if !ok {
fmt.Printf("Player %s Won
", name)
return
}
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed
", name)
close(court)
return
}
fmt.Printf("Player %s Hit %d
", name, ball)
ball++
court <- ball
}
}Buffered Channels
Buffered channels can store a limited number of values, allowing sends to proceed without an immediate receiver until the buffer is full.
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
const (
numberGoroutines = 4
taskLoad = 10
)
var wg sync.WaitGroup
func init() { rand.Seed(time.Now().Unix()) }
func main() {
tasks := make(chan string, taskLoad)
wg.Add(numberGoroutines)
for gr := 1; gr <= numberGoroutines; gr++ {
go worker(tasks, gr)
}
for i := 1; i <= taskLoad; i++ {
tasks <- fmt.Sprintf("Task : %d", i)
}
close(tasks)
wg.Wait()
}
func worker(tasks chan string, worker int) {
defer wg.Done()
for {
task, ok := <-tasks
if !ok {
fmt.Printf("Worker %d: finished
", worker)
return
}
fmt.Printf("Worker %d: start %s
", worker, task)
sleep := rand.Int63n(100)
time.Sleep(time.Duration(sleep) * time.Millisecond)
fmt.Printf("Worker %d: done %s
", worker, task)
}
}These examples illustrate how Go's concurrency primitives—goroutines, channels, atomic operations, and mutexes—enable efficient, safe parallel execution.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
