💻 Développement

dev-go-concurrency-guide

Développement Go avec focus sur la concurrence et les patterns idiomatiques.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-go-concurrency-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-go-concurrency-guide -Launch"

🚀 Déjà installé ?

claude "/dev-go-concurrency-guide"

Ou tapez /dev-go-concurrency-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

GoGolanggoroutinechannelsyncconcurrencyGo modulesGo patternsinterface Go

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-go-concurrency-guide ~/.claude/skills/

Payload du plugin : skills/dev-go-concurrency-guide · source éditable : dev-skills/go-concurrency-guide

📖 Manuel

Go Concurrency Guide

1. Fondamentaux Go — critères de décision

SituationChoix
Méthode modifie le receiverPointer receiver *T
Méthode lit seulementValue receiver T (sauf struct > ~64 bytes)
Comportement partagéInterface implicite (duck typing)
Cleanup garantidefer f.Close() / defer mu.Unlock()
Erreur attendueRetour (T, error), jamais panic
type Store struct{ db *sql.DB }

func (s *Store) Get(ctx context.Context, id int) (User, error) {
    var u User
    err := s.db.QueryRowContext(ctx, "SELECT ...").Scan(&u.Name)
    return u, fmt.Errorf("store.Get %d: %w", id, err)
}

2. Goroutines et channels

Unbuffered = synchrone (handshake). Buffered = découplage, ne pas abuser.

// Done pattern — arrêt propre
done := make(chan struct{})
go func() {
    defer close(done)
    for {
        select {
        case <-ctx.Done():
            return
        case job := <-jobs:
            process(job)
        }
    }
}()

// Fan-out vers N workers
jobs := make(chan Task, 100)
var wg sync.WaitGroup
for range N {                          // Go 1.22+
    wg.Add(1)
    go func() {
        defer wg.Done()
        for t := range jobs { handle(t) }
    }()
}
close(jobs)
wg.Wait()

Règle d'or : le producteur ferme le channel, jamais le consommateur.

3. Patterns de concurrence — quand utiliser quoi

PatternUsageSnippet
Worker poolCPU-bound, N fixemake(chan Job, buf) + N goroutines
PipelineTransformations en chaînechannels en entrée/sortie de chaque stage
SemaphoreLimiter I/O concurrentsem := make(chan struct{}, max)
errgroupGoroutines avec erreur remontéegolang.org/x/sync/errgroup
// Semaphore — max 10 requêtes HTTP parallèles
sem := make(chan struct{}, 10)
for _, url := range urls {
    sem <- struct{}{}
    go func(u string) {
        defer func() { <-sem }()
        fetch(u)
    }(url)
}

// errgroup — propagation d'erreur
g, ctx := errgroup.WithContext(ctx)
for _, item := range items {
    item := item
    g.Go(func() error { return process(ctx, item) })
}
if err := g.Wait(); err != nil { ... }

4. Sync primitives — choix raisonné

// Mutex : protéger un état partagé simple
type Cache struct {
    mu    sync.RWMutex
    store map[string]string
}
func (c *Cache) Get(k string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.store[k]
    return v, ok
}

// Once : init lazy thread-safe
var once sync.Once
var client *http.Client
func getClient() *http.Client {
    once.Do(func() { client = &http.Client{Timeout: 5 * time.Second} })
    return client
}

// Atomic : compteur haute fréquence
var hits atomic.Int64
hits.Add(1)
fmt.Println(hits.Load())

sync.Map : seulement si le profiler le justifie (overhead élevé vs map + RWMutex).

5. Context — règles non négociables

// Timeout sur appel externe
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()                          // toujours defer cancel

resp, err := http.NewRequestWithContext(ctx, "GET", url, nil)

// Propager sans créer de goroutine leak
func worker(ctx context.Context, ch <-chan Work) {
    for {
        select {
        case <-ctx.Done():
            return                      // nettoyage immédiat
        case w, ok := <-ch:
            if !ok { return }
            handle(ctx, w)
        }
    }
}

Ne jamais stocker context.Context dans une struct ; le passer en premier paramètre.

6. Error handling

// Sentinel error
var ErrNotFound = errors.New("not found")

// Error custom avec données
type ValidationError struct{ Field, Msg string }
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation %s: %s", e.Field, e.Msg)
}

// Wrapping + unwrapping
if err := repo.Get(id); err != nil {
    return fmt.Errorf("service.GetUser %d: %w", id, err)
}
if errors.Is(err, ErrNotFound) { ... }
var ve *ValidationError
if errors.As(err, &ve) { ... }

7. Testing

// Table-driven
func TestAdd(t *testing.T) {
    cases := []struct{ a, b, want int }{
        {1, 2, 3}, {0, 0, 0}, {-1, 1, 0},
    }
    for _, tc := range cases {
        t.Run(fmt.Sprintf("%d+%d", tc.a, tc.b), func(t *testing.T) {
            got := Add(tc.a, tc.b)
            assert.Equal(t, tc.want, got)
        })
    }
}

// Race detector — toujours en CI
// go test -race ./...

// Benchmark
func BenchmarkWorkerPool(b *testing.B) {
    b.ResetTimer()
    for b.Loop() { runPool() }     // Go 1.24+ b.Loop()
}

8. Production checklist

# Lint
golangci-lint run ./...

# Profil CPU (30s)
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# Build reproductible
go build -trimpath -ldflags="-s -w" ./cmd/myapp

# Vulnérabilités
govulncheck ./...

Anti-patterns / pièges

PiègeSymptômeCorrection
Goroutine leakGoroutines qui ne se terminent jamaisToujours ctx.Done() ou fermeture du channel
Race conditiongo test -race détecte des accès concurrentssync.Mutex ou ownership clair
Channel blockingDeadlock au runtimeBuffered channel ou goroutine séparée pour envoyer
Capturer variable de loopgo func() { use(i) }() prend la dernière valeuri := i avant le go, ou Go 1.22+ (loop var par itération)
Panic non récupéréCrash du process entierdefer func() { recover() }() dans chaque goroutine long-lived
Context dans structAPI difficile à annulerToujours passer ctx en paramètre
sync.Map par défautPerf dégradéeN'utiliser que pour des maps avec insertions rares et lectures très fréquentes