Modelul Go CSP

Du-te îmbrățișează Comunicarea proceselor secvențiale (CSP), un model formal propus de Tony Hoare în 1978. Principiul fundamental: în loc să aibă mai multe gorutine care citesc și scriu în aceeași variabilă (cu mutex), goroutine comunică prin canal — conducte tipizate care transferă proprietatea datelor.

Runtime-ul Go gestionează programarea goroutinelor pe un grup de fire de execuție a sistemului de operare (în mod implicit chiar la numărul de nuclee disponibile, controlate de GOMAXPROCS). O goroutină este suspendat automat în timpul operațiunilor de blocare (I/O, recv canal), permițând altora goroutine pentru a avansa.

Goroutine: anatomie și ciclu de viață

package main

import (
    "fmt"
    "time"
)

func worker(id int, done chan struct{}) {
    fmt.Printf("Worker %d started\n", id)
    time.Sleep(100 * time.Millisecond)     // simula lavoro
    fmt.Printf("Worker %d finished\n", id)
    done <- struct{}{}                       // segnala completamento
}

func main() {
    done := make(chan struct{}, 5)  // channel bufferizzato per 5

    // Lancia 5 goroutine concorrentemente
    for i := 0; i < 5; i++ {
        go worker(i, done)           // "go" avvia la goroutine
    }

    // Aspetta che tutte completino
    for i := 0; i < 5; i++ {
        <-done                       // riceve dal channel (blocca se vuoto)
    }

    fmt.Println("All workers done")
}

// Costo di una goroutine: ~2-8KB stack (cresce dinamicamente fino a 1GB)
// vs ~1MB per un OS thread
// Go può avere milioni di goroutine attive contemporaneamente

Canal: Tipuri și modele

Channel Unbuffered și Buffered

// Channel unbuffered: sincronizzazione diretta
// Send blocca finché un receiver è pronto
ch := make(chan int)         // unbuffered
go func() { ch <- 42 }()  // blocca finché qualcuno riceve
v := <-ch                   // sblocca il sender

// Channel buffered: coda FIFO con capacità fissata
// Send blocca SOLO se il buffer è pieno
buffered := make(chan int, 10)   // buffer da 10 elementi
buffered <- 1                   // non blocca (buffer ha spazio)
buffered <- 2                   // non blocca
v := <-buffered                 // riceve 1 (FIFO)

// Directional channel types: sicurezza a compile time
func producer(ch chan<- int) { // solo write
    ch <- 42
}

func consumer(ch <-chan int) { // solo read
    v := <-ch
    fmt.Println(v)
}

Model: conductă

// Pipeline: catena di goroutine connesse da channel
// Ogni fase legge dall'input channel e scrive sull'output channel

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)    // IMPORTANTE: chiudi quando finisci
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {  // range su channel riceve finché chiuso
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    // Compone la pipeline
    c := generate(2, 3, 4, 5)
    out := square(c)

    // Consuma l'output
    for v := range out {
        fmt.Println(v)  // 4, 9, 16, 25
    }
}

Selectați: Multiplexare pe canale multiple

select este cel mai puternic cuvânt cheie din Go pentru competiție: așteptați mai multe canale în același timp și execută cazul gata de canal. Dacă mai multe canale sunt gata, el alege la întâmplare.

// Timeout pattern con select
import "time"

func fetchWithTimeout(url string, timeout time.Duration) (string, error) {
    resultCh := make(chan string, 1)
    errCh := make(chan error, 1)

    go func() {
        result, err := fetch(url)
        if err != nil {
            errCh <- err
            return
        }
        resultCh <- result
    }()

    select {
    case result := <-resultCh:
        return result, nil
    case err := <-errCh:
        return "", err
    case <-time.After(timeout):
        return "", fmt.Errorf("timeout after %v", timeout)
    }
}

// Fan-out/fan-in con select e done channel
func merge(channels ...<-chan int) <-chan int {
    merged := make(chan int)
    var wg sync.WaitGroup

    multiplex := func(ch <-chan int) {
        defer wg.Done()
        for v := range ch {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, ch := range channels {
        go multiplex(ch)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}

context.Context: Propagated Erasure

Pachetul context este mecanismul standard al lui Go pentru propagarea ștergerii printr-un lanţ de goroutine. Orice funcție care face I/O sau lucru lung ar trebui să o accepte a context.Context ca prim parametru:

import (
    "context"
    "fmt"
    "time"
)

// Funzione che rispetta la cancellazione
func longOperation(ctx context.Context, id int) error {
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():              // controlla se il context è cancellato
            return ctx.Err()           // errore: context.Canceled o DeadlineExceeded
        default:
            fmt.Printf("Step %d/%d\n", i+1, 10)
            time.Sleep(100 * time.Millisecond)
        }
    }
    return nil
}

func main() {
    // Context con timeout di 500ms
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()  // SEMPRE defer cancel() per liberare le risorse

    err := longOperation(ctx, 1)
    if err != nil {
        fmt.Printf("Cancelled: %v\n", err)  // context deadline exceeded
    }
}

// Context si propaga attraverso le chiamate:
// Handler HTTP -> Service -> Repository -> Database
// Se il client disconnette, il context si cancella e risale tutta la catena

WaitGroup: model de sincronizare

import "sync"

func processAll(items []Item) {
    var wg sync.WaitGroup
    results := make([]Result, len(items))

    for i, item := range items {
        wg.Add(1)
        go func(i int, item Item) {   // passa i e item come parametri!
            defer wg.Done()
            results[i] = process(item) // scrivere indici diversi è safe
        }(i, item)
    }

    wg.Wait()           // blocca finché tutti Done() sono stati chiamati
    fmt.Println(results)
}

// ERRORE COMUNE: closure su variabile del loop (prima di Go 1.22)
// In Go 1.22+ il loop variable è scoped per iterazione -- no problema
for _, item := range items {
    go func() {
        process(item)    // Go 1.22+: safe. Go <1.22: BUG! usa go func(i Item) invece
    }()
}

Detector de curse: Găsirea datelor de cursă

// Compila ed esegui con il race detector integrato
go run -race main.go
go test -race ./...

// Esempio di data race che il detector trova:
var counter int

func increment() {
    counter++  // DATA RACE! lettura + scrittura non atomica
}

// go run -race stamperà:
// WARNING: DATA RACE
// Write at 0x... by goroutine 6:
//   main.increment()
// Previous write at 0x... by goroutine 5:
//   main.increment()

// Fix con atomic o mutex:
import "sync/atomic"
var atomicCounter int64
atomic.AddInt64(&atomicCounter, 1)  // atomico, thread-safe

Goroutine Leak: Cea mai comună eroare din Go

O goroutină care se blochează pe un canal de recepție (nimeni nu trimite vreodată) sau nu primește a nu șterge niciodată contextul este o scurgere de goroutine. Acumulează memorie și CPU. Utilizați întotdeauna context pentru operațiuni lungi și asigurați-vă că fiecare canal are un expeditor și un receptor sau folosiți canale tamponate cu curățare explicită.

Concluzii

Modelul de concurență Go este printre cele mai eficiente pentru serviciile de backend: goroutine ultra-ușoare, canale care previn condițiile de cursă prin design, select pentru multiplexare e context pentru anulare propagată. Cel mai concurent cod Go nu are nevoie de mutexuri explicite.

Articolul următor trece la Python: asyncio.TaskGroup și concurența structurată în Python 3.11+, care aduce în sfârșit în lume siguranță semantică comparabilă cu CSP-ul Go asincron/așteaptă.

Următorul în serie Python asyncio Advanced →