victorzhuk/skills

z-go-concurrency

Goroutines, channels, sync primitives, worker pools, and pipeline patterns for production Go.

ソースを見る

使い方

  1. インストールコマンドをコピーし、自分の環境へ導入する前にソースを確認します。
  2. 必要なツール、認証情報、対応環境を原文で確認します。
  3. 入力と成果物を指定し、出力を資料と照合します。
npx skills add https://github.com/victorzhuk/skills --skill z-go-concurrency

開始前の確認

  • 導入にはモデル契約、API枠、ファイルへのアクセス許可は含まれません。
  • スター数や導入数は活動の指標であり、独立した品質・安全性試験ではありません。
リポジトリの原文

見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。

Go Concurrency

Every goroutine is a liability until proven necessary. Correctness and leak-freedom first.

Principles

  1. Give every goroutine an exit condition and an owner that waits for completion. A WaitGroup joins work; it does not cancel or unblock it.
  2. Channel sends copy values, not referenced data. Choose exclusive ownership transfer, immutable sharing, or synchronized mutation.
  3. Pointer payloads are valid under that protocol. Copied slices, maps, and pointer-bearing structs still share storage; the sender must stop accessing transferred mutable data until ownership returns.
  4. Assign one closer. Close only after all possible sends finish; with multiple senders, join them before closing. sync.Once prevents duplicate closes, not send/close races.
  5. Specify channel direction (chan<-, <-chan) — compiler prevents misuse.
  6. Default to unbuffered channels; buffers mask backpressure.
  7. Guard channel operations that must unblock on cancellation with ctx.Done(). A ready cancellation case has no priority over other ready cases.
  8. Reuse timers when repeated time.After allocations matter; check effective timer semantics before choosing a reset/drain protocol.

Channel state and visibility

  • A nil channel blocks sends and receives; setting a local input to nil disables its select case.
  • A closed, drained channel stays ready and yields zero values. Check ok; return at stream end or disable that input in a multiplexing loop.
  • select evaluates channel operands and send values before choosing a case. case out <- expensive() runs expensive() even if cancellation or default wins.
  • A send synchronizes before completion of its matching receive. Closing synchronizes before a receive that observes closure (ok == false), not merely a receive of earlier buffered data. An unbuffered receive also synchronizes before completion of its matching send.
  • Goroutine exit, time.Sleep, and runtime.Gosched do not join work or establish visibility. Use a channel, lock, WaitGroup, or documented atomic protocol.
  • Atomics do not make a compound invariant atomic. Publishing a pointer atomically does not protect later mutations of its pointee.

Channel vs Mutex vs Atomic

ScenarioPrimitiveWhy
Passing data between goroutineschannelHandoff under an explicit ownership protocol
Goroutine lifecycle / signalschannel + contextClean shutdown via select
Protecting shared struct fieldssync.Mutex / sync.RWMutexSimple critical sections
Independent counters, flagssync/atomicAtomic access; measure contention and cost
Write-once/read-many or disjoint-key map accesssync.MapSpecialized workloads; default to typed map + lock
Cache stampede preventionx/sync/singleflightDeduplicates in-flight calls
Caching an expensive initsync.OnceExecute once, safe for concurrent callers

WaitGroup vs errgroup

NeedUse
Wait for completion, no error collectionsync.WaitGroup
Collect first errorerrgroup.Group
Cancel siblings on first errorerrgroup.WithContext
Bounded concurrencyerrgroup.SetLimit(n)

errgroup.Group.Go(func() error { ... }) — no manual Add/Done; first non-nil error is returned by Wait. This signature has always been func() error; nothing about it is Go-version-gated.

Go 1.25+: wg.Go(f) starts and tracks a task; f must not panic. Start tasks before Wait when the group is empty. Reuse a group for independent work only after previous Wait calls return. With manual Add/Done, register work before starting its goroutine.

Sync Primitives

PrimitiveKey rule
sync.MutexNever hold across I/O; keep critical sections short
sync.RWMutexNever upgrade RLock → Lock (deadlock)
sync/atomicPrefer typed: atomic.Int64, atomic.Bool (Go 1.19+)
sync.MapIndividual operations are safe; multi-operation invariants need coordination
sync.PoolRelease aliases before Put; reset before reuse; entries may disappear at any time
sync.OnceGo 1.21+: OnceFunc, OnceValue, OnceValues

Do not copy locks, WaitGroups, Once/Pool/Map values, or typed atomics after first use, including through containing structs, value receivers, range values, and channel sends. Keep them at stable addresses. Go 1.22 loop declarations can introduce implicit copies; see [[z-go-modernize]].

Common Patterns

Goroutine with clean exit

go
func (w *Worker) run(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        case item, ok := <-w.queue:
            if !ok {
                return
            }
            w.process(item)
        }
    }
}

errgroup with bounded workers

go
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
for _, item := range items {
    g.Go(func() error {
        return process(ctx, item)
    })
}
if err := g.Wait(); err != nil {
    return fmt.Errorf("process batch: %w", err)
}

singleflight — cache stampede prevention

Store sf singleflight.Group on each Service, alongside its repository; do not share a package-level group across independent service instances or copy the service after first use. An ID-only key is valid only when the result depends solely on that ID within the instance. Include tenant, authorization, or other result-affecting scope in an unambiguous key when those vary per call.

go
func (s *Service) fetchUser(ctx context.Context, id string) (*User, error) {
    v, err, _ := s.sf.Do(id, func() (any, error) {
        return s.repo.GetUser(ctx, id)
    })
    if err != nil {
        return nil, fmt.Errorf("fetch user: %w", err)
    }
    return v.(*User), nil
}

Fan-out / fan-in

go
// fanOut: spawn n workers reading from in; each gets its own output channel.
// Each goroutine: defer close(ch), for item := range in { select ch<-/ctx.Done }.
// merge: one goroutine per upstream channel draining → out with ctx.Done guard;
// WaitGroup closes out when all upstreams finish.

Timer reuse in hot loop

With Go 1.23 timer semantics, unreachable timers can be collected before expiry; channel Stop/Reset exclude stale notifications. Go 1.23–1.26 can retain legacy behavior through the main module's version or GODEBUG compatibility settings; Go 1.27 removed asynctimerchan. Reuse avoids repeated allocations, rather than fixing a universal timer leak. The loop below resets only after consuming the previous tick.

go
t := time.NewTimer(interval)
defer t.Stop()
for {
    select {
    case <-ctx.Done():
        return
    case <-t.C:
        work()
        t.Reset(interval)
    }
}

Goroutine spawn checklist

Before every go func:

  • [ ] How does it exit? (context, channel close, done signal)
  • [ ] Can it be signalled to stop?
  • [ ] Is something waiting for it? (WaitGroup / errgroup)
  • [ ] Who owns the channels it uses?
  • [ ] Should this be synchronous instead?

Do not

  • wg.Add inside the goroutine — Wait may return before Add is called.
  • Close a channel while a sender may still use it.
  • Access transferred mutable data concurrently without synchronization.
  • Upgrade RLock to Lock — deadlock.
  • Treat len(ch) or an "is closed" probe as permission to send or close; state can change immediately.
  • Leave select without ctx.Done() in goroutines that run until cancellation.
  • Spawn goroutines without a bound — use errgroup.SetLimit(n) or a semaphore.

Leak detection

go
// go.uber.org/goleak — add to TestMain
func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}
sh
timeout 5m go test -race -timeout 2m -p 2 -parallel 2 ./...
runtime.NumGoroutine()           # runtime count
curl http://localhost:6060/debug/pprof/goroutine?debug=2  # stack dump

Verify

sh
timeout 5m go test -race -timeout 2m -p 2 -parallel 2 ./...
golangci-lint run ./...

Prefer the project's bounded test target. Exercise closed input, cancellation while blocked, and sender completion before close; the race detector alone does not prove liveness.

For singleflight, verify that duplicate keys share work within the intended scope and that independent service instances or tenants cannot share results.

Sources

同じリポジトリから

関連する Skills

すべての Skills
victorzhuk
コミュニティ

z-accessibility

Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".

導入数
1
GitHub Stars
0
更新日
10月22日
victorzhuk
コミュニティ

z-algo-core

DS/algorithm selection judgment for production code, not competitive tricks — hash map for membership/dedup, slice+sort beats a tree until proven, heap for top-k, two-pointer/sliding-window for scans, BFS/DFS/topo-sort for dependency shapes. Triggers on Big-O, hash map vs tree. Does not cover profiling/tuning; see [[z-go-performance]].

導入数
1
GitHub Stars
0
更新日
10月22日
victorzhuk
コミュニティ

z-bdd

BDD acceptance contracts for humans and agents across UI, API, and CLI products. Use for Gherkin, Given-When-Then, E2E acceptance, or scenario evidence. Define repeatable scenarios with explicit assertions and required outcomes. Test selection belongs to [[z-testing-strategy]]. Go harness wiring belongs to [[z-go-bdd]].

導入数
1
GitHub Stars
0
更新日
10月22日
victorzhuk
コミュニティ

z-codegraph

Use an existing CodeGraph integration for call graphs, impact analysis, affected tests, or route discovery. Discover installed tools and index freshness; follow host LSP precedence and verify current source. Do not initialize indexes without user authorization.

導入数
1
GitHub Stars
0
更新日
10月22日