Concurrency(并发)
goroutine 是由 Go 运行时调度的轻量执行单元,channel 和 sync 原语用于建立同步关系。goroutine 不是 Promise,也不是可以无限创建而不管理的后台线程;每个任务都必须有完成、取消和资源释放路径。
goroutine 与 channel
package main
import "fmt"
func main() {
results := make(chan int)
go func() { results <- 21 * 2 }()
fmt.Println(<-results)
}
无缓冲 channel 的发送和接收必须同时准备;有缓冲 channel 允许有限数量的排队,但缓冲区满后发送仍会阻塞。关闭 channel 表示不会再发送值,接收方可使用 value, ok := <-ch 或 for value := range ch 判断结束;发送已关闭 channel 或重复关闭会 panic。
生产者、消费者与关闭责任
通常由唯一的发送方负责关闭 channel,接收方不应在不知道其他发送者的情况下关闭它:
func produce(values []int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, value := range values {
out <- value * 2
}
}()
return out
}
for value := range produce([]int{1, 2, 3}) {
fmt.Println(value)
}
示例片段需要放在包含 fmt 的函数中。返回方向为 <-chan int 可以在类型层面阻止调用方发送或关闭,减少所有权错误。
select、取消与退出
func waitResult(ctx context.Context, results <-chan int) error {
select {
case result, ok := <-results:
if !ok {
return errors.New("result channel closed")
}
fmt.Println(result)
return nil
case <-ctx.Done():
return ctx.Err()
}
}
该函数需要导入 context、errors 和 fmt。select 会等待可执行的分支;多个分支同时就绪时不保证固定优先级。生产代码应使用 context.WithCancel、WithTimeout 或 WithDeadline 传播取消,并让每个发送方在取消后停止发送,否则接收方退出后仍可能泄漏 goroutine。
并发安全与验证
共享内存需要 sync.Mutex、sync/atomic 或 channel 建立 happens-before 关系。“只有一个 goroutine 会写”必须由代码结构保证,而不是根据观察到的执行顺序推断。竞态检测可以发现一类数据竞态,但不能证明没有死锁、活锁、饥饿或业务层重复处理。
go test -race ./...
并发测试还应为任务设置超时,验证取消后所有 goroutine 都能结束,并检查结果是否完整、顺序是否有明确契约。
常见错误
- 启动 goroutine 后没有等待、取消或关闭相关资源。
- 关闭 channel 的责任不清,多个发送方竞争关闭。
- 接收方提前返回,发送方永久阻塞。
- 在锁内执行阻塞 I/O,扩大临界区并降低吞吐。
- 把 channel 当作所有共享状态的替代品,忽略简单互斥锁更清晰的场景。
JavaScript 事件循环通常由单线程驱动;Go goroutine 可能并行执行,必须建立同步关系。context.Context 同时携带截止时间和取消信号,不能直接等同于 AbortController。
延伸阅读
- Context(上下文):请求范围的取消、超时和截止时间。
- Sync(同步原语):互斥锁、等待组和原子操作。
小结
- 并发代码必须设计启动、通信、取消和退出路径。
- channel 负责传递和同步,mutex/atomic 负责保护共享状态。
- 通过方向类型、唯一关闭方和
go test -race验证并发假设。