跳到主要内容

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 := <-chfor 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()
}
}

该函数需要导入 contexterrorsfmtselect 会等待可执行的分支;多个分支同时就绪时不保证固定优先级。生产代码应使用 context.WithCancelWithTimeoutWithDeadline 传播取消,并让每个发送方在取消后停止发送,否则接收方退出后仍可能泄漏 goroutine。

并发安全与验证

共享内存需要 sync.Mutexsync/atomic 或 channel 建立 happens-before 关系。“只有一个 goroutine 会写”必须由代码结构保证,而不是根据观察到的执行顺序推断。竞态检测可以发现一类数据竞态,但不能证明没有死锁、活锁、饥饿或业务层重复处理。

go test -race ./...

并发测试还应为任务设置超时,验证取消后所有 goroutine 都能结束,并检查结果是否完整、顺序是否有明确契约。

常见错误

  • 启动 goroutine 后没有等待、取消或关闭相关资源。
  • 关闭 channel 的责任不清,多个发送方竞争关闭。
  • 接收方提前返回,发送方永久阻塞。
  • 在锁内执行阻塞 I/O,扩大临界区并降低吞吐。
  • 把 channel 当作所有共享状态的替代品,忽略简单互斥锁更清晰的场景。

JavaScript 事件循环通常由单线程驱动;Go goroutine 可能并行执行,必须建立同步关系。context.Context 同时携带截止时间和取消信号,不能直接等同于 AbortController

延伸阅读

小结

  • 并发代码必须设计启动、通信、取消和退出路径。
  • channel 负责传递和同步,mutex/atomic 负责保护共享状态。
  • 通过方向类型、唯一关闭方和 go test -race 验证并发假设。