要理解Go语言中并发(Concurrency)与并行(Parallelism)的区别,需要从基本概念、Go中的实现方式和核心差异三个层面展开,结合Go的特性(如goroutine
、GOMAXPROCS
、GPM模型
)说明:
一、基本概念区别
并发与并行是计算机科学中的核心概念,但本质不同:
维度 | 并发(Concurrency) | 并行(Parallelism) |
---|---|---|
定义 | 逻辑上的“同时”:同一时间段内交替执行多个任务(如单核CPU上的多任务调度),同一时间点只有一个任务在执行。 | 物理上的“同时”:同一时间点多个任务在多个处理器(多核CPU)上同时执行(如多核CPU上的多线程并行)。 |
核心 | 任务的调度与切换(通过时间片轮转等策略),让用户“感觉”多个任务同时进行。 | 任务的物理并行执行(依赖多核硬件),真正提升计算效率。 |
比喻 | 一个厨师同时处理多个订单(切菜、炒菜、摆盘交替进行)。 | 多个厨师同时处理多个订单(每个厨师负责一个订单,同时工作)。 |
二、Go中的实现方式区别
Go语言从语言层面支持并发(通过goroutine
),并通过** runtime 调度**实现并行(依赖GOMAXPROCS
和多核CPU):
1. 并发的实现:goroutine(轻量级线程)
-
定义:
goroutine
是Go语言中的轻量级执行单元(比操作系统线程更轻量,栈初始大小仅4KB,可动态扩展),由Go runtime而非操作系统内核调度。 -
实现方式:使用
go
关键字启动一个goroutine
,例如:func printHello() { fmt.Println("Hello from goroutine") } func main() { go printHello() // 启动一个goroutine执行printHello fmt.Println("Hello from main") time.Sleep(time.Second) // 等待goroutine执行完毕 }
-
调度逻辑:默认情况下,Go runtime使用1个逻辑处理器(Logical Processor),所有
goroutine
在该逻辑处理器上交替执行(并发)。即使启动多个goroutine
,同一时间点只有一个goroutine
在执行(单核CPU场景)。
2. 并行的实现:GOMAXPROCS与多核CPU
-
依赖条件:要实现并行,需要满足两个条件:
- 多个逻辑处理器:通过
runtime.GOMAXPROCS(n)
设置(n
为逻辑处理器数量,默认等于CPU核数); - 多核CPU:物理硬件支持多个核心同时工作。
- 多个逻辑处理器:通过
-
实现方式:当
GOMAXPROCS
设置为大于1时,Go runtime会将goroutine
分配到多个逻辑处理器上,每个逻辑处理器对应一个操作系统线程,从而实现物理并行。例如:import ( "fmt" "runtime" "time" ) func printNumbers(id int) { for i := 1; i <= 5; i++ { fmt.Printf("Goroutine %d: %d\n", id, i) time.Sleep(100 * time.Millisecond) } } func main() { runtime.GOMAXPROCS(2) // 设置2个逻辑处理器 go printNumbers(1) // 启动goroutine 1 go printNumbers(2) // 启动goroutine 2 time.Sleep(1 * time.Second) // 等待所有goroutine执行完毕 }
-
执行结果:两个
goroutine
会在两个逻辑处理器上同时执行(多核CPU场景),输出结果为交替或同时打印(取决于CPU核数)。
三、核心差异总结
维度 | 并发(Go) | 并行(Go) |
---|---|---|
执行方式 | 单个逻辑处理器上交替执行多个goroutine (逻辑同时)。 | 多个逻辑处理器上同时执行多个goroutine (物理同时)。 |
依赖条件 | 不需要多核CPU(单核即可),由Go runtime调度。 | 需要多核CPU + GOMAXPROCS 设置(大于1)。 |
默认行为 | Go的默认行为(GOMAXPROCS 默认等于CPU核数,但单个goroutine 仍为并发)。 | 需要显式设置GOMAXPROCS (如runtime.GOMAXPROCS(2) )才能开启。 |
资源消耗 | goroutine 轻量(栈初始4KB),可启动10万级goroutine 而不消耗过多资源。 | 依赖操作系统线程(栈初始1-2MB),线程数量过多会导致资源消耗增大(上下文切换开销)。 |
设计目标 | 提高资源利用率(如I/O密集型任务,通过goroutine 切换避免阻塞)。 | 提高计算效率(如CPU密集型任务,通过多核并行加速)。 |
四、关键结论(来自Go作者Rob Pike的观点)
Go语言的并发模型遵循**“并发不是并行,但并发实现了并行”**的设计哲学:
- 并发是程序的设计结构(将任务分解为独立执行的片段),例如使用
goroutine
分解任务; - 并行是程序的运行时特性(依赖硬件和调度),例如通过
GOMAXPROCS
让goroutine
在多核上并行执行; - Go的
GPM模型
(Goroutine
、Processor
、Machine
)实现了goroutine
到线程的多路复用,既支持高效的并发(单个线程处理多个goroutine
),也支持灵活的并行(多个线程处理多个goroutine
)。
总结
- 并发是Go语言的核心特性(通过
goroutine
实现,轻量、高效),用于处理多任务场景(如I/O密集型应用); - 并行是Go语言的扩展特性(通过
GOMAXPROCS
和多核CPU实现),用于提升计算密集型任务的效率; - 理解两者的区别,才能正确设计Go程序:I/O密集型任务用并发(
goroutine
),CPU密集型任务用并行(GOMAXPROCS
+多核)。