| name | go-concurrency |
| user-invocable | false |
| description | Use when Go concurrency with goroutines, channels, and sync patterns. Use when writing concurrent Go code. |
| allowed-tools | ["Bash","Read"] |
Go Concurrency
Master Go's concurrency model using goroutines, channels, and synchronization
primitives for building concurrent applications.
Goroutines
Creating goroutines:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello()
go func() {
fmt.Println("Hello from anonymous goroutine")
}()
time.Sleep(time.Second)
}
Goroutines with parameters:
func printNumber(n int) {
fmt.Println(n)
}
func main() {
for i := 0; i < 10; i++ {
go printNumber(i)
}
time.Sleep(time.Second)
}
Channels
Basic channel operations:
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
fmt.Println(value)
}
Buffered channels:
func main() {
ch := make(chan string, 2)
ch <- "first"
ch <- "second"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Channel direction:
func send(ch chan<- int) {
ch <- 42
}
func receive(ch <-chan int) int {
return <-ch
}
func main() {
ch := make(chan int)
go send(ch)
value := receive(ch)
fmt.Println(value)
}
Closing channels:
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for value := range ch {
fmt.Println(value)
}
value, ok := <-ch
fmt.Printf("Value: %d, Open: %v\n", value, ok)
}
Select Statement
Multiplexing channels:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
Select with default:
func main() {
ch := make(chan int, 1)
select {
case val := <-ch:
fmt.Println(val)
default:
fmt.Println("No value ready")
}
}
Select with timeout:
func main() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "result"
}()
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(time.Second):
fmt.Println("Timeout")
}
}
Worker Pools
Implementing worker pool pattern:
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
<-results
}
}
sync.WaitGroup
Waiting for goroutines to complete:
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
sync.Mutex
Protecting shared state:
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
var wg sync.WaitGroup
counter := Counter{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println(counter.Value())
}
sync.RWMutex
Read-write locks:
type Cache struct {
mu sync.RWMutex
items map[string]string
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.items[key]
return val, ok
}
func (c *Cache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}
func main() {
cache := Cache{items: make(map[string]string)}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
cache.Get("key")
}()
}
wg.Wait()
}
sync.Once
Execute once initialization:
var (
instance *Database
once sync.Once
)
type Database struct {
conn string
}
func GetDatabase() *Database {
once.Do(func() {
fmt.Println("Initializing database")
instance = &Database{conn: "connected"}
})
return instance
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
db := GetDatabase()
fmt.Println(db.conn)
}()
}
wg.Wait()
}
Context Package
Using context for cancellation:
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d cancelled\n", id)
return
default:
fmt.Printf("Worker %d working\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
for i := 1; i <= 3; i++ {
go worker(ctx, i)
}
time.Sleep(2 * time.Second)
cancel()
time.Sleep(time.Second)
}
Context with timeout:
func slowOperation(ctx context.Context) error {
select {
case <-time.After(3 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(
context.Background(),
2*time.Second,
)
defer cancel()
err := slowOperation(ctx)
if err != nil {
fmt.Println("Operation timed out:", err)
}
}
Context with values:
func processRequest(ctx context.Context) {
userID := ctx.Value("userID")
fmt.Println("Processing for user:", userID)
}
func main() {
ctx := context.WithValue(
context.Background(),
"userID",
"user123",
)
processRequest(ctx)
}
Error Handling in Concurrent Code
Using errgroup:
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
"time"
)
func fetchUser(ctx context.Context, id int) error {
time.Sleep(time.Second)
if id == 3 {
return fmt.Errorf("user %d not found", id)
}
fmt.Printf("Fetched user %d\n", id)
return nil
}
func main() {
g, ctx := errgroup.WithContext(context.Background())
userIDs := []int{1, 2, 3, 4, 5}
for _, id := range userIDs {
id := id
g.Go(func() error {
return fetchUser(ctx, id)
})
}
if err := g.Wait(); err != nil {
fmt.Println("Error:", err)
}
}
Fan-Out Fan-In Pattern
Distributing work and collecting results:
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func merge(cs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(cs))
for _, c := range cs {
go func(ch <-chan ) {
wg.Done()
n := ch {
out <- n
}
}(c)
}
{
wg.Wait()
(out)
}()
out
}
{
in := generator(, , , , )
c1 := square(in)
c2 := square(in)
n := merge(c1, c2) {
fmt.Println(n)
}
}
When to Use This Skill
Use go-concurrency when you need to:
- Execute multiple operations concurrently
- Build concurrent servers or workers
- Implement producer-consumer patterns
- Process data streams concurrently
- Handle multiple I/O operations simultaneously
- Implement timeout and cancellation
- Coordinate multiple goroutines
- Build fan-out/fan-in pipelines
- Share state safely between goroutines
- Implement rate limiting or throttling
Best Practices
- Use channels for communication, mutexes for state
- Close channels from sender side only
- Always use WaitGroup to wait for goroutines
- Pass contexts for cancellation and deadlines
- Use buffered channels judiciously
- Protect shared state with mutexes
- Avoid goroutine leaks with proper cleanup
- Use select with default for non-blocking ops
- Prefer sync.Once for initialization
- Document goroutine ownership and lifecycle
Common Pitfalls
- Goroutine leaks (forgetting to exit)
- Race conditions from unprotected shared state
- Deadlocks from improper channel usage
- Sending on closed channels (panics)
- Not checking channel close status
- Overusing mutexes instead of channels
- Creating too many goroutines
- Forgetting to call WaitGroup.Done()
- Passing loop variables to goroutines
- Not handling context cancellation
Resources