Goroutine 和 Channel
Go 的并发模型主要建立在两个核心概念之上:
Goroutine (协程):Goroutine 是 Go 语言轻量级的执行线程。你可以将其理解为比操作系统线程更轻量级的“并发执行单元”。成千上万的 Goroutine 可以在一个操作系统线程上复用。
创建 Goroutine: 使用 go 关键字来启动一个 Goroutine。
go func() { // 这是在 Goroutine 中执行的代码 fmt.Println("Hello from a goroutine!") }() // 主 Goroutine 继续执行 time.Sleep(1 * time.Second) // 简单等待 Goroutine 完成
特点:
轻量级: 创建和销毁 Goroutine 的开销非常小,可以创建成百万级别的 Goroutine。
独立执行: 每个 Goroutine 都有自己的调用栈,它们独立于其他 Goroutine 执行。
GMP 模型: Go 的运行时(runtime)通过一个称为 GMP(Goroutine, M, P)的模型来管理 Goroutine。Goroutine 是要执行的任务,M 是操作系统线程,P 是逻辑处理器(Processor),每个 P 都可以调度 Goroutine 在 M 上执行。这使得 Go 能够有效地利用多核 CPU。
Channel (通道): Channel 是 Go 中 Goroutine 之间进行通信和同步的“管道”。通过 Channel,Goroutine 可以安全地发送和接收数据。
创建 Channel: 使用 make 函数创建。
ch := make(chan int) // 创建一个可以传递 int 类型数据的通道
发送和接收数据:
发送: ch <- value
接收: value := <-ch
阻塞行为:
向未缓冲通道发送数据时,发送者会阻塞,直到接收者准备好接收。
从未缓冲通道接收数据时,接收者会阻塞,直到发送者准备好发送。
如果通道有缓冲,发送和接收操作只有在缓冲满或空时才会阻塞。
用途:
数据传递: 允许 Goroutine 之间安全地交换数据。
同步: Channel 的阻塞特性可以用于实现 Goroutine 之间的同步。
信号: 可以使用 Channel 发送信号,通知其他 Goroutine 某个事件已经发生。
关闭 Channel: 使用 close(ch) 函数关闭通道。关闭后的通道可以继续接收数据,但无法再发送数据。尝试向已关闭的通道发送数据会引发 panic。
避免常见的并发陷阱
Goroutine 泄露:当 Goroutine 启动后,但由于某种原因(如死锁、无限循环、未正确关闭 Channel)而无法退出,就会导致 Goroutine 泄露,占用资源,影响程序性能。
解决: 确保 Goroutine 有明确的退出机制,或者通过 Channel 来取消。
死锁:当两个或多个 Goroutine 互相等待对方释放资源时,就会发生死锁。
常见原因:
死锁的 Channel 操作,如发送给已满的缓冲通道,或接收来自已关闭且为空的通道
锁的嵌套使用不当。
解决: 仔细分析 Goroutine 之间的依赖关系,避免循环等待。
竞态条件 (Race Condition):
当多个 Goroutine 同时访问和修改共享数据,并且操作的顺序会影响最终结果时,就会发生竞态条件。
检测: Go 提供了内置的竞态条件检测工具:go run -race your_program.go 或 go build -race your_program.go。
undefined