npx skills add https://github.com/cxuu/golang-skills --skill go-concurrency本技能涵盖 Google Go 风格指南和 Uber Go 风格指南中的并发模式和最佳实践,包括 goroutine 管理、通道使用、互斥锁处理和同步。
规范性:当你创建 goroutine 时,要明确它们何时或是否会退出。
Goroutine 可能会因在通道发送或接收上阻塞而泄漏。即使没有其他 goroutine 引用该通道,垃圾收集器也不会终止在通道上阻塞的 goroutine。
即使 goroutine 没有泄漏,在不再需要时让它们继续运行也会导致:
Panic:在已关闭的通道上发送会导致 panic
数据竞争:在不需要结果后修改仍在使用的输入
内存问题:长生命周期的 goroutine 导致不可预测的内存使用
资源泄漏:阻止未使用的对象被垃圾回收
// 错误:在已关闭的通道上发送会导致 panic ch := make(chan int) ch <- 42 close(ch) ch <- 13 // panic
编写并发代码时,应使 goroutine 的生命周期显而易见。将与同步相关的代码限制在函数作用域内,并将逻辑分解为同步函数。
// 良好:Goroutine 生命周期清晰
func (w *Worker) Run(ctx context.Context) error {
var wg sync.WaitGroup
for item := range w.q {
wg.Add(1)
go func() {
defer wg.Done()
process(ctx, item) // 当上下文取消时返回
}()
}
wg.Wait() // 防止创建的 goroutine 比此函数存活更久
}
// 错误:不关心 goroutine 何时结束
func (w *Worker) Run() {
for item := range w.q {
go process(item) // 这个什么时候结束?如果它永不结束呢?
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
每个 goroutine 都必须有一个可预测的停止机制:
来源:Uber Go 风格指南
// 错误:无法停止或等待此 goroutine
go func() {
for {
flush()
time.Sleep(delay)
}
}()
// 良好:使用停止/完成通道模式进行受控关闭
var (
stop = make(chan struct{}) // 通知 goroutine 停止
done = make(chan struct{}) // 通知我们 goroutine 已退出
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-ticker.C:
flush()
case <-stop:
return
}
}
}()
// 关闭方式:
close(stop) // 通知 goroutine 停止
<-done // 并等待它退出
使用 sync.WaitGroup 等待多个 goroutine:
var wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 工作...
}()
}
wg.Wait()
使用完成通道等待单个 goroutine:
done := make(chan struct{})
go func() {
defer close(done)
// 工作...
}()
<-done // 等待 goroutine 完成
init() 函数不应创建 goroutine。如果一个包需要后台 goroutine,应公开一个对象,该对象通过一个方法(Close、Stop、Shutdown)来管理 goroutine 的生命周期,以停止并等待它。
来源:Uber Go 风格指南
// 错误:创建了不可控的后台 goroutine
func init() {
go doWork()
}
// 良好:显式的生命周期管理
type Worker struct {
stop chan struct{}
done chan struct{}
}
func NewWorker() *Worker {
w := &Worker{
stop: make(chan struct{}),
done: make(chan struct{}),
}
go w.doWork()
return w
}
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
使用 go.uber.org/goleak 来测试创建 goroutine 的包中是否存在 goroutine 泄漏。
原则:永远不要在不清楚 goroutine 如何停止的情况下启动它。
sync.Mutex 和 sync.RWMutex 的零值是有效的,因此几乎不需要使用指向互斥锁的指针。
来源:Uber Go 风格指南
// 错误:不必要的指针
mu := new(sync.Mutex)
mu.Lock()
// 良好:零值有效
var mu sync.Mutex
mu.Lock()
如果你通过指针使用结构体,互斥锁应是一个非指针字段。不要将互斥锁嵌入到结构体中,即使该结构体未导出。
// 错误:嵌入的互斥锁将 Lock/Unlock 暴露为 API 的一部分
type SMap struct {
sync.Mutex // Lock() 和 Unlock() 成为 SMap 的方法
data map[string]string
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}
// 良好:命名字段将互斥锁作为实现细节隐藏
type SMap struct {
mu sync.Mutex
data map[string]string
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}
在错误的示例中,Lock 和 Unlock 方法意外地成为了导出 API 的一部分。在良好的示例中,互斥锁是对调用者隐藏的实现细节。
规范性:优先使用同步函数而非异步函数。
// 良好:同步函数 - 调用者控制并发性
func ProcessItems(items []Item) ([]Result, error) {
var results []Result
for _, item := range items {
result, err := processItem(item)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
// 调用者可以在需要时添加并发性:
go func() {
results, err := ProcessItems(items)
// 处理结果
}()
建议性:在调用者端移除不必要的并发性非常困难(有时甚至不可能)。让调用者在需要时添加并发性。
规范性:尽可能指定通道方向。
// 良好:指定了方向 - 所有权清晰
func sum(values <-chan int) int {
total := 0
for v := range values {
total += v
}
return total
}
// 错误:未指定方向 - 允许意外误用
func sum(values chan int) (out int) {
for v := range values {
out += v
}
close(values) // 错误!这可以编译,但不应发生。
}
通道通常应具有大小为 1 的缓冲区或无缓冲区。任何其他大小都必须经过仔细审查。考虑:
来源:Uber Go 风格指南
// 错误:任意的缓冲区大小
c := make(chan int, 64) // "对任何人来说都应该足够了!"
// 良好:经过深思熟虑的大小
c := make(chan int, 1) // 大小为 1
c := make(chan int) // 无缓冲,大小为 0
func produce(out chan<- int) { /* 只发送 */ }
func consume(in <-chan int) { /* 只接收 */ }
func transform(in <-chan int, out chan<- int) { /* 双向 */ }
使用 go.uber.org/atomic 进行类型安全的原子操作。标准的 sync/atomic 包操作原始类型(int32、int64 等),很容易忘记一致地使用原子操作。
来源:Uber Go 风格指南
// 错误:容易忘记原子操作
type foo struct {
running int32 // 原子操作
}
func (f *foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
return // 已在运行
}
// 启动 Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // 竞争!忘记了 atomic.LoadInt32
}
// 良好:类型安全的原子操作
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
return // 已在运行
}
// 启动 Foo
}
func (f *foo) isRunning() bool {
return f.running.Load() // 不会意外地非原子读取
}
go.uber.org/atomic 包通过隐藏底层类型来增加类型安全性,并包含方便的原子类型,如 atomic.Bool、atomic.Int64 等。
建议性:当线程安全性从操作类型不明显时,应进行文档说明。
Go 用户假设只读操作可以安全地并发使用,而修改操作则不能。在以下情况下应文档化并发性:
Lookup使用缓冲通道作为空闲列表来重用已分配的缓冲区。这种“泄漏缓冲区”模式使用带有 default 的 select 进行非阻塞操作。
有关完整模式、示例以及使用 sync.Pool 的生产环境替代方案,请参阅 references/BUFFER-POOLING.md。
| 主题 | 指导原则 | 类型 |
|---|---|---|
| Goroutine 生命周期 | 明确退出条件 | 规范性 |
| 即忘模式 | 不要这样做 - 始终要有停止机制 | 规范性 |
| 零值互斥锁 | 有效;不要使用指针 | 建议性 |
| 互斥锁嵌入 | 不要嵌入;使用命名字段 | 建议性 |
| 同步函数 | 优先于异步 | 规范性 |
| 通道方向 | 始终指定 | 规范性 |
| 通道大小 | 默认一个或无缓冲 | 建议性 |
| 原子操作 | 使用 go.uber.org/atomic | 建议性 |
| 并发文档 | 当不明显时进行文档说明 | 建议性 |
在创建 goroutine 之前,回答:
| 反模式 | 问题 | 修复方法 |
|---|---|---|
| 即忘的 goroutine | 资源泄漏,未定义行为 | 使用 WaitGroup、完成通道或上下文 |
| init() 中的 goroutine | 不可控的生命周期 | 使用具有 Shutdown 方法的显式对象 |
| 嵌入的互斥锁 | 将 Lock/Unlock 泄漏到 API 中 | 使用命名的 mu 字段 |
| 指向互斥锁的指针 | 不必要的间接引用 | 零值有效 |
| 任意的通道缓冲区 | 隐藏的阻塞问题 | 默认为 0 或 1 |
| 原始的 sync/atomic | 容易忘记原子读取 | 使用 go.uber.org/atomic |
| 未文档化的线程安全性 | 调用者可能发生竞争 | 当不明确时进行文档说明 |
每周安装量
143
仓库
GitHub Stars
34
首次出现
Jan 27, 2026
安全审计
安装于
github-copilot135
gemini-cli134
codex134
kimi-cli133
opencode133
amp132
This skill covers concurrency patterns and best practices from Google's Go Style Guide and Uber's Go Style Guide, including goroutine management, channel usage, mutex handling, and synchronization.
Normative : When you spawn goroutines, make it clear when or whether they exit.
Goroutines can leak by blocking on channel sends or receives. The garbage collector will not terminate a goroutine blocked on a channel even if no other goroutine has a reference to the channel.
Even when goroutines do not leak, leaving them in-flight when no longer needed causes:
Panics : Sending on a closed channel causes a panic
Data races : Modifying still-in-use inputs after the result isn't needed
Memory issues : Unpredictable memory usage from long-lived goroutines
Resource leaks : Preventing unused objects from being garbage collected
// Bad: Sending on closed channel causes panic ch := make(chan int) ch <- 42 close(ch) ch <- 13 // panic
Write concurrent code so goroutine lifetimes are evident. Keep synchronization-related code constrained within function scope and factor logic into synchronous functions.
// Good: Goroutine lifetimes are clear
func (w *Worker) Run(ctx context.Context) error {
var wg sync.WaitGroup
for item := range w.q {
wg.Add(1)
go func() {
defer wg.Done()
process(ctx, item) // Returns when context is cancelled
}()
}
wg.Wait() // Prevent spawned goroutines from outliving this function
}
// Bad: Careless about when goroutines finish
func (w *Worker) Run() {
for item := range w.q {
go process(item) // When does this finish? What if it never does?
}
}
Every goroutine must have a predictable stop mechanism:
Source : Uber Go Style Guide
// Bad: No way to stop or wait for this goroutine
go func() {
for {
flush()
time.Sleep(delay)
}
}()
// Good: Stop/done channel pattern for controlled shutdown
var (
stop = make(chan struct{}) // tells the goroutine to stop
done = make(chan struct{}) // tells us that the goroutine exited
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-ticker.C:
flush()
case <-stop:
return
}
}
}()
// To shut down:
close(stop) // signal the goroutine to stop
<-done // and wait for it to exit
Use sync.WaitGroup for multiple goroutines:
var wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// work...
}()
}
wg.Wait()
Use a done channel for a single goroutine:
done := make(chan struct{})
go func() {
defer close(done)
// work...
}()
<-done // wait for goroutine to finish
init() functions should not spawn goroutines. If a package needs a background goroutine, expose an object that manages the goroutine's lifetime with a method (Close, Stop, Shutdown) to stop and wait for it.
Source : Uber Go Style Guide
// Bad: Spawns uncontrollable background goroutine
func init() {
go doWork()
}
// Good: Explicit lifecycle management
type Worker struct {
stop chan struct{}
done chan struct{}
}
func NewWorker() *Worker {
w := &Worker{
stop: make(chan struct{}),
done: make(chan struct{}),
}
go w.doWork()
return w
}
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
Use go.uber.org/goleak to test for goroutine leaks in packages that spawn goroutines.
Principle : Never start a goroutine without knowing how it will stop.
The zero-value of sync.Mutex and sync.RWMutex is valid, so you almost never need a pointer to a mutex.
Source : Uber Go Style Guide
// Bad: Unnecessary pointer
mu := new(sync.Mutex)
mu.Lock()
// Good: Zero-value is valid
var mu sync.Mutex
mu.Lock()
If you use a struct by pointer, the mutex should be a non-pointer field. Do not embed the mutex on the struct, even if the struct is not exported.
// Bad: Embedded mutex exposes Lock/Unlock as part of API
type SMap struct {
sync.Mutex // Lock() and Unlock() become methods of SMap
data map[string]string
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}
// Good: Named field keeps mutex as implementation detail
type SMap struct {
mu sync.Mutex
data map[string]string
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}
With the bad example, Lock and Unlock methods are unintentionally part of the exported API. With the good example, the mutex is an implementation detail hidden from callers.
Normative : Prefer synchronous functions over asynchronous functions.
// Good: Synchronous function - caller controls concurrency
func ProcessItems(items []Item) ([]Result, error) {
var results []Result
for _, item := range items {
result, err := processItem(item)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
// Caller can add concurrency if needed:
go func() {
results, err := ProcessItems(items)
// handle results
}()
Advisory : It is quite difficult (sometimes impossible) to remove unnecessary concurrency at the caller side. Let the caller add concurrency when needed.
Normative : Specify channel direction where possible.
// Good: Direction specified - clear ownership
func sum(values <-chan int) int {
total := 0
for v := range values {
total += v
}
return total
}
// Bad: No direction - allows accidental misuse
func sum(values chan int) (out int) {
for v := range values {
out += v
}
close(values) // Bug! This compiles but shouldn't happen.
}
Channels should usually have a size of one or be unbuffered. Any other size must be subject to scrutiny. Consider:
Source : Uber Go Style Guide
// Bad: Arbitrary buffer size
c := make(chan int, 64) // "Ought to be enough for anybody!"
// Good: Deliberate sizing
c := make(chan int, 1) // Size of one
c := make(chan int) // Unbuffered, size of zero
func produce(out chan<- int) { /* send-only */ }
func consume(in <-chan int) { /* receive-only */ }
func transform(in <-chan int, out chan<- int) { /* both directions */ }
Use go.uber.org/atomic for type-safe atomic operations. The standard sync/atomic package operates on raw types (int32, int64, etc.), making it easy to forget to use atomic operations consistently.
Source : Uber Go Style Guide
// Bad: Easy to forget atomic operation
type foo struct {
running int32 // atomic
}
func (f *foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
return // already running
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // race! forgot atomic.LoadInt32
}
// Good: Type-safe atomic operations
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
return // already running
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running.Load() // can't accidentally read non-atomically
}
The go.uber.org/atomic package adds type safety by hiding the underlying type and includes convenient types like atomic.Bool, atomic.Int64, etc.
Advisory : Document thread-safety when it's not obvious from the operation type.
Go users assume read-only operations are safe for concurrent use, and mutating operations are not. Document concurrency when:
Lookup that mutates LRU stateUse a buffered channel as a free list to reuse allocated buffers. This "leaky buffer" pattern uses select with default for non-blocking operations.
See references/BUFFER-POOLING.md for the full pattern with examples and production alternatives using sync.Pool.
| Topic | Guidance | Type |
|---|---|---|
| Goroutine lifetimes | Make exit conditions clear | Normative |
| Fire-and-forget | Don't do it - always have stop mechanism | Normative |
| Zero-value mutexes | Valid; don't use pointers | Advisory |
| Mutex embedding | Don't embed; use named field | Advisory |
| Synchronous functions | Prefer over async | Normative |
| Channel direction | Always specify | Normative |
| Channel size | One or none by default | Advisory |
| Atomic operations | Use go.uber.org/atomic | Advisory |
| Concurrency docs | Document when not obvious | Advisory |
Before spawning a goroutine, answer:
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Fire-and-forget goroutines | Resource leaks, undefined behavior | Use WaitGroup, done channel, or context |
| Goroutines in init() | Uncontrollable lifecycle | Use explicit object with Shutdown method |
| Embedded mutexes | Leaks Lock/Unlock into API | Use named mu field |
| Pointer to mutex | Unnecessary indirection | Zero-value is valid |
| Arbitrary channel buffers | Hidden blocking issues | Default to 0 or 1 |
| Raw sync/atomic | Easy to forget atomic reads | Use go.uber.org/atomic |
| Undocumented thread-safety | Callers may race | Document when unclear |
Weekly Installs
143
Repository
GitHub Stars
34
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot135
gemini-cli134
codex134
kimi-cli133
opencode133
amp132
GSAP React 动画库使用指南:useGSAP Hook 与最佳实践
1,400 周安装
确定申告书类收集指南:gather技能助你高效整理税务申报所需文件
249 周安装
JavaScript概念文档测试编写器 - 自动生成Vitest测试,验证代码示例准确性
249 周安装
sadd:do-in-steps 任务分解与代理协调框架 - 多步骤AI代理工作流管理
249 周安装
价值主张模板:6步JTBD框架生成客户价值主张,产品策略与营销定位指南
249 周安装
产品头脑风暴工具:从产品经理、设计师、工程师多视角生成创意,筛选最佳方案
249 周安装
JavaScript 事实核查器 - 验证 33 JS 概念准确性,确保代码示例与 MDN 文档正确
249 周安装