{"id":"7dc66d53-95df-4513-a649-6641531ff276","shortId":"Xxjt5f","kind":"skill","title":"go-concurrency","tagline":"Use when writing concurrent Go code — goroutines, channels, mutexes, or thread-safety guarantees. Also use when parallelizing work, fixing data races, or protecting shared state, even if the user doesn't explicitly mention concurrency primitives. Does not cover context.Context pa","description":"# Go Concurrency\n\n## Goroutine Lifetimes\n\n> **Normative**: When you spawn goroutines, make it clear when or whether they\n> exit.\n\nGoroutines can leak by blocking on channel sends/receives. The GC **will not\nterminate** a blocked goroutine even if no other goroutine holds a reference to\nthe channel. Even non-leaking in-flight goroutines cause panics (send on closed\nchannel), data races, memory issues, and resource leaks.\n\n### Core Rules\n\n1. **Every goroutine needs a stop mechanism** — a predictable end time, a\n   cancellation signal, or both\n2. **Code must be able to wait** for the goroutine to finish\n3. **No goroutines in `init()`** — expose lifecycle methods (`Close`, `Stop`,\n   `Shutdown`) instead\n4. **Keep synchronization scoped** — constrain to function scope, factor logic\n   into synchronous functions\n\n```go\n// Good: Clear lifetime with WaitGroup\nvar wg sync.WaitGroup\nfor item := range queue {\n    wg.Add(1)\n    go func() { defer wg.Done(); process(ctx, item) }()\n}\nwg.Wait()\n```\n\n```go\n// Bad: No way to stop or wait\ngo func() { for { flush(); time.Sleep(delay) } }()\n```\n\n**Test for leaks** with [go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak).\n\n> **Principle**: Never start a goroutine without knowing how it will stop.\n\n> Read [references/GOROUTINE-PATTERNS.md](references/GOROUTINE-PATTERNS.md) when\n> implementing stop/done channel patterns, goroutine waiting strategies, or\n> lifecycle-managed workers.\n\n---\n\n## Share by Communicating\n\n> \"Do not communicate by sharing memory; instead, share memory by communicating.\"\n\nThis is Go's foundational concurrency design principle. Use **channels** for\nownership transfer and orchestration — when one goroutine produces a value and\nanother consumes it. Use **mutexes** when multiple goroutines access shared\nstate and channels would add unnecessary complexity.\n\n**Default to channels.** Fall back to `sync.Mutex` / `sync.RWMutex` when the\nproblem is naturally about protecting a shared data structure (e.g., a cache or\ncounter) rather than passing data between goroutines.\n\n---\n\n## Synchronous Functions\n\n> **Normative**: Prefer synchronous functions over asynchronous ones.\n\n| Benefit | Why |\n|---|---|\n| Localized goroutines | Lifetimes easier to reason about |\n| Avoids leaks and races | Easier to prevent resource leaks and data races |\n| Easier to test | Check input/output without polling |\n| Caller flexibility | Caller adds concurrency when needed |\n\n> **Advisory**: It is quite difficult (sometimes impossible) to remove\n> unnecessary concurrency at the caller side. Let the caller add concurrency\n> when needed.\n\n> Read [references/GOROUTINE-PATTERNS.md](references/GOROUTINE-PATTERNS.md) when\n> writing synchronous-first APIs that callers may wrap in goroutines.\n\n---\n\n## Zero-value Mutexes\n\nThe zero-value of `sync.Mutex` and `sync.RWMutex` is valid — almost never need\na pointer to a mutex.\n\n```go\n// Good: Zero-value is valid    // Bad: Unnecessary pointer\nvar mu sync.Mutex                mu := new(sync.Mutex)\n```\n\n**Don't embed mutexes** — use a named `mu` field to keep `Lock`/`Unlock` as\nimplementation details, not exported API.\n\n> Read [references/SYNC-PRIMITIVES.md](references/SYNC-PRIMITIVES.md) when\n> implementing mutex-protected structs or deciding how to structure mutex fields.\n\n---\n\n## Channel Direction\n\n> **Normative**: Specify channel direction where possible.\n\nDirection prevents errors (compiler catches closing a receive-only channel),\nconveys ownership, and is self-documenting.\n\n```go\nfunc produce(out chan<- int) { /* send-only */ }\nfunc consume(in <-chan int)  { /* receive-only */ }\nfunc transform(in <-chan int, out chan<- int) { /* both */ }\n```\n\n### Channel Size: One or None\n\nChannels should have size **zero** (unbuffered) or **one**. Any other size\nrequires justification for:\n\n- How the size was determined\n- What prevents the channel from filling under load\n- What happens when writers block\n\n```go\nc := make(chan int)    // unbuffered — Good\nc := make(chan int, 1) // size one — Good\nc := make(chan int, 64) // arbitrary — needs justification\n```\n\n> Read [references/SYNC-PRIMITIVES.md](references/SYNC-PRIMITIVES.md) when\n> reviewing detailed channel direction examples with error-prone patterns.\n\n---\n\n## Atomic Operations\n\nUse `atomic.Bool`, `atomic.Int64`, etc. (stdlib `sync/atomic` since Go 1.19, or\n[go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic)) for type-safe\natomic operations. Raw `int32`/`int64` fields make it easy to forget atomic\naccess on some code paths.\n\n```go\n// Good: Type-safe              // Bad: Easy to forget\nvar running atomic.Bool          var running int32 // atomic\nrunning.Store(true)              atomic.StoreInt32(&running, 1)\nrunning.Load()                   running == 1 // race!\n```\n\n> Read [references/SYNC-PRIMITIVES.md](references/SYNC-PRIMITIVES.md) when\n> choosing between sync/atomic and go.uber.org/atomic, or implementing atomic\n> state flags in structs.\n\n---\n\n## Documenting Concurrency\n\n> **Advisory**: Document thread-safety when it's not obvious from the operation\n> type.\n\nGo users assume read-only operations are safe for concurrent use, and mutating\noperations are not. Document concurrency when:\n\n1. **Read vs mutating is unclear** — e.g., a `Lookup` that mutates LRU state\n2. **API provides synchronization** — e.g., thread-safe clients\n3. **Interface has concurrency requirements** — document in type definition\n\n---\n\n## Context Usage\n\n> For context.Context guidance (parameter placement, struct storage, custom\n> types, derivation patterns), see the dedicated\n> [go-context](../go-context/SKILL.md) skill.\n\n---\n\n## Buffer Pooling with Channels\n\nUse a buffered channel as a free list to reuse allocated buffers. This \"leaky\nbuffer\" pattern uses `select` with `default` for non-blocking operations.\n\n> Read [references/BUFFER-POOLING.md](references/BUFFER-POOLING.md) when\n> implementing a worker pool with reusable buffers or choosing between\n> channel-based pools and `sync.Pool`.\n\n---\n\n## Advanced Patterns\n\n> Read [references/ADVANCED-PATTERNS.md](references/ADVANCED-PATTERNS.md) when\n> implementing request-response multiplexing with channels of channels, or\n> CPU-bound parallel computation across cores.\n\n---\n\n## Related Skills\n\n- **Context propagation**: See [go-context](../go-context/SKILL.md) when passing cancellation, deadlines, or request-scoped values through goroutines\n- **Error handling**: See [go-error-handling](../go-error-handling/SKILL.md) when propagating errors from goroutines or using errgroup\n- **Defensive hardening**: See [go-defensive](../go-defensive/SKILL.md) when protecting shared state at API boundaries or using defer for cleanup\n- **Interface design**: See [go-interfaces](../go-interfaces/SKILL.md) when choosing receiver types for types with sync primitives\n\n### External Resources\n\n- [Never start a goroutine without knowing how it will\n  stop](https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop)\n  — Dave Cheney\n- [Rethinking Classical Concurrency\n  Patterns](https://www.youtube.com/watch?v=5zXAHh5tJqQ) — Bryan Mills\n  (GopherCon 2018)\n- [When Go programs end](https://changelog.com/gotime/165) — Go Time podcast\n- [go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak) — Goroutine leak\n  detector for testing\n- [go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic) — Type-safe\n  atomic operations","tags":["concurrency","golang","skills","cxuu","agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor","llm"],"capabilities":["skill","source-cxuu","skill-go-concurrency","topic-agent-skills","topic-ai-agent","topic-ai-assistant","topic-claude","topic-claude-code","topic-codex","topic-cursor","topic-golang","topic-llm"],"categories":["golang-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cxuu/golang-skills/go-concurrency","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cxuu/golang-skills","source_repo":"https://github.com/cxuu/golang-skills","install_from":"skills.sh"}},"qualityScore":"0.491","qualityRationale":"deterministic score 0.49 from registry signals: · indexed on github topic:agent-skills · 82 github stars · SKILL.md body (7,388 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-02T12:55:17.572Z","embedding":null,"createdAt":"2026-04-18T22:13:07.625Z","updatedAt":"2026-05-02T12:55:17.572Z","lastSeenAt":"2026-05-02T12:55:17.572Z","tsv":"'/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop)':923 '/atomic,':670 '/atomic](https://pkg.go.dev/go.uber.org/atomic)':957 '/atomic](https://pkg.go.dev/go.uber.org/atomic))':613 '/go-context/skill.md':764,846 '/go-defensive/skill.md':880 '/go-error-handling/skill.md':865 '/go-interfaces/skill.md':899 '/goleak](https://pkg.go.dev/go.uber.org/goleak)':949 '/goleak](https://pkg.go.dev/go.uber.org/goleak).':208 '/gotime/165)':943 '/watch?v=5zxahh5tjqq)':932 '1':112,179,573,655,658,714 '1.19':609 '2':128,727 '2018':936 '3':140,736 '4':152 '64':581 'abl':132 'access':280,630 'across':836 'add':286,359,381 'advanc':815 'advisori':363,680 'alloc':780 'almost':414 'also':18 'anoth':272 'api':393,456,728,886 'arbitrari':582 'assum':696 'asynchron':326 'atom':599,618,629,650,673,961 'atomic.bool':602,646 'atomic.int64':603 'atomic.storeint32':653 'avoid':337 'back':293 'bad':189,429,640 'base':811 'benefit':328 'block':66,76,561,793 'bound':833 'boundari':887 'bryan':933 'buffer':766,772,781,784,805 'c':563,569,577 'cach':310 'caller':356,358,376,380,395 'cancel':124,849 'catch':485 'caus':97 'chan':503,511,519,522,565,571,579 'changelog.com':942 'changelog.com/gotime/165)':941 'channel':11,68,88,102,226,259,284,291,473,477,491,525,530,552,591,769,773,810,827,829 'channel-bas':809 'check':352 'cheney':925 'choos':664,807,901 'classic':927 'cleanup':892 'clear':56,167 'client':735 'close':101,148,486 'code':9,129,633 'communic':238,241,249 'compil':484 'complex':288 'comput':835 'concurr':3,7,38,46,255,360,373,382,679,704,712,739,928 'constrain':156 'consum':273,509 'context':745,763,840,845 'context.context':43,748 'convey':492 'core':110,837 'counter':312 'cover':42 'cpu':832 'cpu-bound':831 'ctx':185 'custom':754 'data':24,103,306,316,347 'dave':924 'dave.cheney.net':922 'dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop)':921 'deadlin':850 'decid':467 'dedic':760 'default':289,789 'defens':874,879 'defer':182,890 'definit':744 'delay':201 'deriv':756 'design':256,894 'detail':453,590 'detector':952 'determin':548 'difficult':367 'direct':474,478,481,592 'document':498,678,681,711,741 'doesn':34 'e.g':308,720,731 'easi':626,641 'easier':333,341,349 'emb':440 'end':121,940 'errgroup':873 'error':483,596,858,863,868 'error-pron':595 'etc':604 'even':30,78,89 'everi':113 'exampl':593 'exit':61 'explicit':36 'export':455 'expos':145 'extern':909 'factor':160 'fall':292 'field':446,472,623 'fill':554 'finish':139 'first':392 'fix':23 'flag':675 'flexibl':357 'flight':95 'flush':199 'forget':628,643 'foundat':254 'free':776 'func':181,197,500,508,516 'function':158,164,320,324 'gc':71 'go':2,8,45,165,180,188,196,252,422,499,562,608,635,694,762,844,862,878,897,938,944 'go-concurr':1 'go-context':761,843 'go-defens':877 'go-error-handl':861 'go-interfac':896 'go.uber.org':207,612,669,948,956 'go.uber.org/atomic,':668 'go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic)':955 'go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic))':611 'go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak)':947 'go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak).':206 'good':166,423,568,576,636 'gophercon':935 'goroutin':10,47,53,62,77,82,96,114,137,142,213,228,267,279,318,331,399,857,870,914,950 'guarante':17 'guidanc':749 'handl':859,864 'happen':558 'harden':875 'hold':83 'implement':224,452,461,672,799,821 'imposs':369 'in-flight':93 'init':144 'input/output':353 'instead':151,245 'int':504,512,520,523,566,572,580 'int32':621,649 'int64':622 'interfac':737,893,898 'issu':106 'item':175,186 'justif':542,584 'keep':153,448 'know':215,916 'leak':64,92,109,204,338,345,951 'leaki':783 'let':378 'lifecycl':146,233 'lifecycle-manag':232 'lifetim':48,168,332 'list':777 'load':556 'local':330 'lock':449 'logic':161 'lookup':722 'lru':725 'make':54,564,570,578,624 'manag':234 'may':396 'mechan':118 'memori':105,244,247 'mention':37 'method':147 'mill':934 'mu':433,435,445 'multipl':278 'multiplex':825 'must':130 'mutat':707,717,724 'mutex':12,276,403,421,441,463,471 'mutex-protect':462 'name':444 'natur':301 'need':115,362,384,416,583 'never':210,415,911 'new':436 'non':91,792 'non-block':791 'non-leak':90 'none':529 'normat':49,321,475 'obvious':689 'one':266,327,527,537,575 'oper':600,619,692,700,708,794,962 'orchestr':264 'ownership':261,493 'pa':44 'panic':98 'parallel':21,834 'paramet':750 'pass':315,848 'path':634 'pattern':227,598,757,785,816,929 'placement':751 'podcast':946 'pointer':418,431 'poll':355 'pool':767,802,812 'possibl':480 'predict':120 'prefer':322 'prevent':343,482,550 'primit':39,908 'principl':209,257 'problem':299 'process':184 'produc':268,501 'program':939 'prone':597 'propag':841,867 'protect':27,303,464,882 'provid':729 'queue':177 'quit':366 'race':25,104,340,348,659 'rang':176 'rather':313 'raw':620 'read':220,385,457,585,660,698,715,795,817 'read-on':697 'reason':335 'receiv':489,514,902 'receive-on':488,513 'refer':85 'references/advanced-patterns.md':818,819 'references/buffer-pooling.md':796,797 'references/goroutine-patterns.md':221,222,386,387 'references/sync-primitives.md':458,459,586,587,661,662 'relat':838 'remov':371 'request':823,853 'request-respons':822 'request-scop':852 'requir':541,740 'resourc':108,344,910 'respons':824 'rethink':926 'reus':779 'reusabl':804 'review':589 'rule':111 'run':645,648,654,657 'running.load':656 'running.store':651 'safe':617,639,702,734,960 'safeti':16,684 'scope':155,159,854 'see':758,842,860,876,895 'select':787 'self':497 'self-docu':496 'send':99,506 'send-on':505 'sends/receives':69 'share':28,236,243,246,281,305,883 'shutdown':150 'side':377 'signal':125 'sinc':607 'size':526,533,540,546,574 'skill':765,839 'skill-go-concurrency' 'sometim':368 'source-cxuu' 'spawn':52 'specifi':476 'start':211,912 'state':29,282,674,726,884 'stdlib':605 'stop':117,149,193,219,920 'stop/done':225 'storag':753 'strategi':230 'struct':465,677,752 'structur':307,470 'sync':907 'sync.mutex':295,409,434,437 'sync.pool':814 'sync.rwmutex':296,411 'sync.waitgroup':173 'sync/atomic':606,666 'synchron':154,163,319,323,391,730 'synchronous-first':390 'termin':74 'test':202,351,954 'thread':15,683,733 'thread-saf':732 'thread-safeti':14,682 'time':122,945 'time.sleep':200 'topic-agent-skills' 'topic-ai-agent' 'topic-ai-assistant' 'topic-claude' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-golang' 'topic-llm' 'transfer':262 'transform':517 'true':652 'type':616,638,693,743,755,903,905,959 'type-saf':615,637,958 'unbuff':535,567 'unclear':719 'unlock':450 'unnecessari':287,372,430 'usag':746 'use':4,19,258,275,442,601,705,770,786,872,889 'user':33,695 'valid':413,428 'valu':270,402,407,426,855 'var':171,432,644,647 'vs':716 'wait':134,195,229 'waitgroup':170 'way':191 'wg':172 'wg.add':178 'wg.done':183 'wg.wait':187 'whether':59 'without':214,354,915 'work':22 'worker':235,801 'would':285 'wrap':397 'write':6,389 'writer':560 'www.youtube.com':931 'www.youtube.com/watch?v=5zxahh5tjqq)':930 'zero':401,406,425,534 'zero-valu':400,405,424","prices":[{"id":"da5195ec-e35c-4702-9d62-0aff84db5c2f","listingId":"7dc66d53-95df-4513-a649-6641531ff276","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cxuu","category":"golang-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:13:07.625Z"}],"sources":[{"listingId":"7dc66d53-95df-4513-a649-6641531ff276","source":"github","sourceId":"cxuu/golang-skills/go-concurrency","sourceUrl":"https://github.com/cxuu/golang-skills/tree/main/skills/go-concurrency","isPrimary":false,"firstSeenAt":"2026-04-18T22:13:07.625Z","lastSeenAt":"2026-05-02T12:55:17.572Z"}],"details":{"listingId":"7dc66d53-95df-4513-a649-6641531ff276","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cxuu","slug":"go-concurrency","github":{"repo":"cxuu/golang-skills","stars":82,"topics":["agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor","go","golang","llm"],"license":"apache-2.0","html_url":"https://github.com/cxuu/golang-skills","pushed_at":"2026-03-15T19:32:10Z","description":"AI Agent Skills for idiomatic, production-ready Go code, distilled from Google, Uber, Community","skill_md_sha":"b385c487a0c148c4308e035dd57d78637c7f0e0b","skill_md_path":"skills/go-concurrency/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cxuu/golang-skills/tree/main/skills/go-concurrency"},"layout":"multi","source":"github","category":"golang-skills","frontmatter":{"name":"go-concurrency","license":"Apache-2.0","description":"Use when writing concurrent Go code — goroutines, channels, mutexes, or thread-safety guarantees. Also use when parallelizing work, fixing data races, or protecting shared state, even if the user doesn't explicitly mention concurrency primitives. Does not cover context.Context patterns (see go-context).","compatibility":"Requires go.uber.org/atomic for atomic operation wrappers"},"skills_sh_url":"https://skills.sh/cxuu/golang-skills/go-concurrency"},"updatedAt":"2026-05-02T12:55:17.572Z"}}