Understanding Go Concurrency: Goroutines, Scheduler, Threads and Synchronization
The article explains Go’s concurrency model, detailing how goroutines are lightweight work units scheduled by the Go runtime onto logical processors, the role of the scheduler, differences between concurrency and parallelism, thread limits, and practical synchronization tools such as WaitGroup, atomic operations, and mutexes.
This article introduces the principles of threads and coroutines in Go, aiming to provide developers with practical experience and insights.
1. Introduction
Go’s syntax and runtime have built‑in support for concurrency. When a function is started as a goroutine , the runtime treats it as an independent work unit that is scheduled onto available logical processors.
2. Go Scheduler
The Go scheduler is a sophisticated component that manages all created goroutines and allocates execution time. It sits on top of the operating system, binding OS threads to Go logical processors (P). The scheduler fully controls which goroutine runs on which logical processor at any moment. Go’s concurrency model follows the Communicating Sequential Processes (CSP) paradigm, using channels for communication instead of locks.
3. CPU Information (macOS)
CPU details can be obtained with the following command:
sysctl machdep.cpuSample output (truncated) shows the number of cores, threads, cache sizes, etc.:
machdep.cpu.core_count: 8 machdep.cpu.thread_count: 16 machdep.cpu.cache.size: 256 machdep.cpu.vendor: GenuineIntelThese values indicate an 8‑core CPU with hyper‑threading (16 logical threads).
4. Process, Thread and Goroutine
When an application starts, the OS creates a process that contains resources such as memory space, file handles and threads. A thread is an execution context scheduled by the OS. A goroutine is a lightweight Go execution unit that runs independently of other goroutines.
Illustrations (omitted) show the relationship between processes, threads and goroutines.
5. Logical Processors and Local Run Queues
Each logical processor (P) is bound to an OS thread (M). Since Go 1.5 the runtime creates one logical processor per physical CPU core by default.
When a goroutine is ready to run, it is first placed in the global run queue, then moved to a local run queue of a logical processor. The scheduler then executes goroutines from the local queue.
When a goroutine performs a blocking system call, the thread is detached from the logical processor, a new thread is created, and another goroutine from the local queue is scheduled. For network I/O, the goroutine is moved to a poller and re‑attached when the I/O becomes ready.
6. Concurrency vs Parallelism
Concurrency means managing many tasks that may be interleaved; parallelism means executing multiple tasks simultaneously on different physical CPUs. Go achieves concurrency by switching goroutines across threads, reducing idle time. True parallelism requires multiple logical processors and physical CPUs.
7. Thread Limits
The Go runtime limits a program to 10 000 OS threads by default. This limit can be changed with runtime/debug.SetMaxThreads . Exceeding the limit causes a crash.
8. Basic Usage of Runtime Functions
Common runtime‑related functions:
runtime.Gosched() – yields the processor.
runtime.NumCPU() – returns the number of CPU cores.
runtime.GOMAXPROCS(n) – sets the maximum number of CPUs that can be used simultaneously.
runtime.Goexit() – terminates the current goroutine (defer statements still run).
runtime.NumGoroutine() – returns the number of existing goroutines.
runtime.GOOS – target operating system.
9. Waiting for Goroutines (sync.WaitGroup)
Example:
package main
import (
"time"
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
say2("hello", &wg)
say2("world", &wg)
fmt.Println("over!")
}
func say2(s string, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
for i := 0; i < 3; i++ {
fmt.Println(s)
}
}Output:
hello
hello
hello
world
world
world
over!10. Atomic Operations (sync/atomic)
Atomic functions for integer types (int32, int64, uint32, uint64, uintptr):
func AddT(addr *T, delta T) (new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)Atomic Value type provides generic load/store:
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})Example of Value usage:
package main
import (
"fmt"
"sync/atomic"
)
type T struct{ a, b, c int }
func main() {
var v atomic.Value
v.Store(T{1,2,3})
fmt.Println(v) // {{1 2 3}}
old := v.Swap(T{4,5,6})
fmt.Println("old:", old)
fmt.Println("v:", v)
swapped := v.CompareAndSwap(T{1,2,3}, T{7,8,9})
fmt.Println(swapped, v)
swapped = v.CompareAndSwap(T{4,5,6}, T{7,8,9})
fmt.Println(swapped, v)
}Output (truncated):
{{1 2 3}
old: {1 2 3}
v: {{4 5 6}}
false {{4 5 6}}
true {{7 8 9}}11. Mutex (sync.Mutex)
Example of protecting a shared counter:
// Package main demonstrates a mutex-protected critical section
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\n", 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()
}
}Output: 4 . The mutex ensures that only one goroutine accesses the critical section at a time.
12. Additional Notes
Atomic Value.Store can only be called once with a concrete type; subsequent stores must use the same type, otherwise a panic occurs.
CompareAndSwap only updates the value when the old value matches the current value.
Mutexes are not tied to a specific goroutine; any goroutine can lock and unlock.
13. References
1. Golang 入门:理解并发与并行 2. 浅谈内存管理单元 (MMU) 3. 一个进程最多能创建多少个线程? 4. Golang 入门:等待 goroutine 完成任务 5. Golang 中 runtime 的使用 6. sync/atomic 标准库提供的原子操作 7. Go 学习笔记(23)— 并发(02) 8. Go 标准库——sync.Mutex 互斥锁 9. 寄存器数目
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
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.