Golang 并发机制及 CSP 并发模型
Golang 是一门为并发而生的语言,其并发机制基于 CSP(Communicating Sequential Processes,通信顺序过程) 模型。CSP 是一种描述并发系统中交互模式的正式语言,强调通过通信来共享内存,而不是通过共享内存来通信。
CSP 模型简介
CSP 模型由 Tony Hoare 在 20 世纪 70 年代提出,是一种基于消息传递的并发模型。其核心思想是:
- 通信代替共享:并发实体(如 Goroutine)通过 Channel 进行通信,而不是直接共享内存。
- Channel 是第一类对象:Channel 是 CSP 模型的核心,用于在并发实体之间传递消息。
- 同步通信:发送和接收操作是阻塞的,确保消息的同步传递。
CSP 模型的优势在于:
- 避免了传统多线程编程中的锁竞争问题。
- 简化了并发编程的复杂性。
- 提高了程序的可读性和可维护性。
Golang 中的 CSP 实现
Golang 通过 Goroutine 和 Channel 实现了 CSP 模型。
1. Goroutine
Goroutine 是 Golang 中的并发执行单位,类似于线程,但比线程更轻量。其特点包括:
- 用户态调度:Goroutine 由 Go 运行时调度,避免了内核态和用户态的切换开销。
- 低成本创建:每个 Goroutine 的栈空间初始很小(通常为 2KB),可以轻松创建成千上万的 Goroutine。
- 动态扩展:Goroutine 的栈空间会根据需要动态扩展和收缩。
2. Channel
Channel 是 Goroutine 之间的通信机制,用于传递数据。其特点包括:
- 类型安全:Channel 有明确的类型,只能传递指定类型的数据。
- 同步通信:发送和接收操作是阻塞的,确保数据的同步传递。
- 双向或单向:Channel 可以是双向的,也可以是单向的(只发送或只接收)。
GPM 调度模型
Golang 的并发调度由 GPM 模型 实现,即:
- G(Goroutine):用户级的轻量级线程,每个 Goroutine 保存自己的上下文信息。
- P(Processor):逻辑处理器,负责调度 Goroutine 和线程(M)之间的关联关系。
- M(Machine):操作系统线程,负责执行 Goroutine。
GPM 的工作原理
- 初始化:Go 程序启动时,会创建多个 P(默认等于 CPU 核心数),每个 P 绑定一个 M。
- Goroutine 调度 :
- 每个 P 维护一个本地 Goroutine 队列(Local Runqueue)。
- 当一个 Goroutine 被创建时,会被放入某个 P 的本地队列中。
- M 从绑定的 P 的本地队列中获取 Goroutine 并执行。
- 阻塞处理 :
- 如果 Goroutine 发生阻塞(如 IO 操作),M 会释放 P,P 会绑定到另一个 M 继续执行其他 Goroutine。
- 当阻塞的 Goroutine 恢复时,会被放入全局队列(Global Runqueue)或某个 P 的本地队列中。
- 负载均衡 :
- 如果某个 P 的本地队列为空,它会从全局队列或其他 P 的本地队列中"偷取" Goroutine。
- 如果全局队列也为空,M 会进入休眠状态,直到有新的 Goroutine 被创建。
是 否 GPM 调度模型 G Goroutine P Processor M Machine 用户级轻量级线程 保存上下文信息 逻辑处理器 调度 Goroutine 和 M 的关系 操作系统线程 执行 Goroutine 维护本地 Goroutine 队列 从队列中获取 Goroutine 执行 Goroutine 是否阻塞? 释放 PM 绑定到其他 P 继续执行 阻塞恢复后放入全局队列 其他 P 从全局队列中偷取 Goroutine
以下是 Golang 中使用 Goroutine 和 Channel 实现并发通信的示例:
cpp
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d started job %d\n", id, job)
time.Sleep(time.Second) // 模拟任务执行
fmt.Printf("Worker %d finished job %d\n", id, job)
results <- job * 2 // 将结果发送到 results Channel
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// 启动 3 个 Worker Goroutine
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 发送任务到 jobs Channel
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 从 results Channel 中接收结果
for a := 1; a <= numJobs; a++ {
fmt.Println("Result:", <-results)
}
}