Go'da Goroutinler ve Kanallar: Sıralı Süreçlerin İletişimi ile Pratik Eşzamanlılık
Go, İletişim Ardışık Süreçleri uygular: ultra hafif goroutinler, borular gibi kanallar Güvenli iletişim için yazın, çoğullama için seçin, senkronizasyon için WaitGroup'u seçin ve Yayılmış silme için context.Context - çoğu durumda tümü açık muteksler olmadan vakaların.
Go CSP Modeli
Git sarıl Sıralı Süreçlerin İletişimi (CSP)resmi bir model Tony Hoare tarafından 1978'de önerildi. Temel prensip: birden fazla goroutine sahip olmak yerine Aynı değişkeni okuyan ve ona yazan (mutex ile), goroutinler aracılığıyla iletişim kurar kanal — veri sahipliğini aktaran yazılı kanallar.
Go çalışma zamanı, bir işletim sistemi iş parçacığı havuzundaki goroutinlerin zamanlamasını yönetir (varsayılan olarak hatta
tarafından kontrol edilen mevcut çekirdek sayısına göre GOMAXPROCS). Bir gorutin
Bloklama işlemleri (G/Ç, kanal alımı) sırasında otomatik olarak askıya alınır ve diğerlerine izin verilir
ilerlemek için goroutine.
Goroutine: Anatomi ve Yaşam Döngüsü
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
Kanal: Türler ve Desenler
Kanal Arabelleğe Alınmamış ve Arabelleğe Alınmış
// 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)
}
Desen: Boru Hattı
// 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
}
}
Seçin: Çoklu Kanallarda Çoğullama
select Go'da rekabet için en güçlü anahtar kelime: bekle
aynı anda birden fazla kanalı çalıştırır ve kanal hazır durumunu yürütür. Birden fazla kanal varsa
onlar hazır, rastgele seçiyor.
// 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: Yayılmış Silme
Paket context Go'nun silme işlemini yaymaya yönelik standart mekanizmasıdır
bir goroutin zinciri aracılığıyla. G/Ç yapan veya uzun süreli çalışma yapan herhangi bir işlev bunu kabul etmelidir
bir context.Context ilk parametre olarak:
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: Senkronizasyon Modeli
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
}()
}
Yarış Dedektörü: Yarış Verilerini Bulma
// 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 Sızıntısı: Go'daki En Yaygın Hata
Bir alma kanalında sıkışıp kalan (hiç kimse göndermez) veya almayan bir goroutine
İçeriği asla silmemek goroutine sızıntısıdır. Bellek ve CPU biriktirin. Her zaman kullan
context uzun işlemler için ve her kanalın bir göndericisinin olduğundan emin olun
ve bir alıcı kullanın veya açık temizleme özelliğine sahip ara belleğe alınmış kanalları kullanın.
Sonuçlar
Go'nun eşzamanlılık modeli, arka uç hizmetleri için en etkili modellerden biridir: goroutine
Ultra hafif, tasarımı gereği yarış koşullarını engelleyen kanallar, select çoğullama için
e context yayılan iptal için. Rakip Go kodlarının çoğu
açık mutekslere ihtiyaç duymaz.
Sonraki makale Python'a geçiyor: asyncio.TaskGroup ve yapılandırılmış rekabet Sonunda Go'nun CSP'sine benzer anlamsal güvenliği dünyaya getiren Python 3.11+'da eşzamansız/beklemede.







