Go 高并发架构
后端架构733 字预计 2 分钟阅读
剖析 Go 语言并发调度的核心机制,包括 GMP 模型、通道的最佳实践以及并发控制策略。
引言
Go 语言因其原生支持高并发而迅速在互联网后端架构中占据了主导地位。它抛弃了传统的操作系统级线程,改用更轻量级的协程(Goroutine)来实现海量并发处理。本文将深入探讨 Go 语言并发调度及高并发架构设计。
GMP 调度模型深度解析
GMP 的核心定义
Go 的调度器主要由三个实体构成:
- G (Goroutine):协程。每个协程拥有独立的栈空间,其初始化大小仅为 2KB。
- M (Machine):物理线程。Go 调度器将 G 分配到 M 上执行,M 与操作系统的物理内核线程一一对应。
- P (Processor):逻辑处理器。P 包含了运行 Go 代码所需的上下文资源和本地 G 队列。
调度策略与工作窃取
Go 调度器的一大核心优势是其高效的任务调度算法:
- 工作窃取(Work Stealing):当某个 P 的本地队列空闲时,它会尝试从其他 P 的本地队列中“窃取”一半的 G 来运行,而不是让物理线程 M 进入睡眠状态。
- 抢占式调度:在 Go 1.14 之后,引入了基于信号的抢占式调度。这避免了某个协程因为死循环等计算密集型操作无限期占用 CPU,从而导致其他协程饿死。
通道的机制与陷阱
CSP 并发模型
Go 提倡“通过通信共享内存,而不是通过共享内存来通信”的 CSP 哲学。Channel(通道)是 Goroutine 之间传递数据的安全信道。
无缓冲与有缓冲通道
- 无缓冲通道:发送和接收是同步的,会造成阻塞,常用于精确的同步控制。
- 有缓冲通道:只有当缓冲区满时发送才会阻塞,缓冲区空时接收才会阻塞,常用于异步任务队列或限流。
常见内存泄漏场景
如果不正确处理通道,很容易导致内存泄漏。例如,在发送端向一个没有接收者的通道发送数据,或者接收端从一个已关闭但没有发送者的通道读取数据,都会导致 Goroutine 永久阻塞,无法被垃圾回收。
并发控制与同步
sync.WaitGroup
用于等待一组并发协程执行完毕。在使用时,务必将 Add 操作放在协程启动之前,并使用 defer Done() 来确保计数器被正确递减,防止发生死锁。
Context 上下文传递
在复杂的微服务调用链中,context.Context 用于在 Goroutine 之间传递取消信号、超时时间以及请求上下文信息。这使得我们能够在父级请求超时或被主动取消时,优雅地通知并终止所有下游子协程,避免无效的计算资源消耗。