在 Go 语言中,理解 并发 (Concurrency)、并行 (Parallelism) 和 异步 (Asynchrony) 的区别是进阶的关键。Go 的设计哲学深受这些概念的影响。
1. 核心概念对比 (Analogy)
我们可以用“咖啡馆”来做类比:
| 概念 | 类比场景 | 关注点 |
|---|---|---|
| 并发 (Concurrency) | 一个服务员同时为多桌客人服务。他在 A 桌点完菜,不等菜上桌,就去 B 桌倒水。他是在处理多件事,但某一瞬间只能做一件事。 | 结构 (Structure):如何组织代码以处理多个任务。 |
| 并行 (Parallelism) | 多个服务员同时工作。服务员 A 在给 A 桌点菜,服务员 B 同时在给 B 桌倒水。他们在执行多件事。 | 执行 (Execution):在多核 CPU 上同时运行。 |
| 异步 (Asynchrony) | 客人点完餐后拿到一个取餐号,然后回座位玩手机。等厨房做好了,会通过广播(回调/信号)通知他。 | 非阻塞 (Non-blocking):发起请求后立即返回,不原地等待结果。 |
2. Go 语言中的实现
A. 并发 (Concurrency) —— Go 的强项
Go 通过 Goroutine (协程) 实现并发。Go 的口号是:“不要通过共享内存来通信,而要通过通信来共享内存。” 并发是 Go 程序的设计属性。即使在单核 CPU 上,你也可以开启 100 万个协程。
B. 并行 (Parallelism) —— 硬件支撑
Go 运行时(Runtime)会自动将并发的协程调度到多个系统线程上。如果你的电脑有多个 CPU 核心,Go 就会自动实现并行。
你可以通过 runtime.GOMAXPROCS(n) 来限制并行使用的核心数。
C. 异步 (Asynchrony) —— “伪同步”写法
在 Node.js 中,异步通常通过 callback, Promise, async/await 实现。
在 Go 中,异步逻辑是用同步的方式写的。当你发起一个网络请求时,当前的协程会“阻塞”,但 Go 运行时的底层其实是异步非阻塞的(使用 epoll/kqueue),它会自动把 CPU 让给其他协程。
3. 代码演示与详解
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func task(name string, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 3; i++ {
fmt.Printf("任务 %s: 正在处理第 %d 步\n", name, i)
// 模拟耗时操作(这里会触发协程切换,体现并发)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
// 1. 并行度设置:查看当前系统的 CPU 核心数
cpuCores := runtime.NumCPU()
fmt.Printf("系统核心数: %d\n", cpuCores)
// 2. 这里的 WaitGroup 用于同步等待所有协程完成(类似于 Promise.all)
var wg sync.WaitGroup
fmt.Println("--- 程序开始运行 ---")
// 3. 启动两个并发任务
wg.Add(2)
go task("A", &wg) // 开启协程 A
go task("B", &wg) // 开启协程 B
// 这里的代码继续执行,体现了“异步”发起的特性
fmt.Println("主线程:我已经下达了任务,现在我去忙别的了...")
wg.Wait() // 阻塞等待 A 和 B 完成
fmt.Println("--- 所有任务完成 ---")
}
4. 深度对比:Go vs Node.js
| 特性 | Go 语言 | Node.js (JavaScript) |
|---|---|---|
| 模型 | CSP (通信顺序进程) | Event Loop (事件循环) |
| 线程 | 多线程 (M:N 调度) | 单线程 (通过异步 I/O 模拟并发) |
| 阻塞感 | 看起来是同步阻塞的,实则异步。代码顺序执行,逻辑清晰。 | 必须使用 await 或回调,否则会产生异步副作用。 |
| 复杂任务 | 擅长 CPU 密集型 + I/O 密集型。 | 擅长 I/O 密集型,CPU 密集型会阻塞事件循环。 |
总结:如何理解?
- 并发是逻辑上的:你在代码里写了
go func(),你的程序就具备了并发处理的能力(结构)。 - 并行是物理上的:当你的程序运行在多核机器上,Go 自动让这些
go func()在不同的核心上同时跑(效率)。 - 异步是体验上的:在 Go 里,你不需要写复杂的
then或callback。Go 让你用最简单的同步代码,享受高性能的异步底层。
一句话:Go 的伟大之处在于,它用并发(协程)的简单模型,完美利用了并行的硬件能力,并屏蔽了异步编程的复杂性。