Go’da Concurrency: Goroutines ve Channels

Go’nun en güçlü özelliklerinden biri concurrency (eşzamanlılık) desteğidir. Bu yazıda goroutines ve channels’ı inceleyeceğiz.

Goroutines Nedir?

Goroutines, Go’nin hafif iş parçacıklarıdır (threads). Binlerce hatta milyonlarca goroutine aynı anda çalışabilir.

package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    fmt.Printf("Merhaba %s!\n", name)
}

func main() {
    // Normal fonksiyon çağrısı
    sayHello("Ahmet")

    // Goroutine olarak çalıştırma
    go sayHello("Mehmet")
    go sayHello("Ayşe")

    // Ana goroutine'in bitmesini beklemek için
    time.Sleep(time.Millisecond * 100)
}

Anonim Goroutines

go func() {
    fmt.Println("Anonim goroutine")
}()

// Parametre ile
go func(msg string) {
    fmt.Println(msg)
}("Merhaba")

Channels

Channels, goroutines arasında iletişim kurmak için kullanılır:

// Channel oluşturma
ch := make(chan int)

// Gönderme
go func() {
    ch <- 42  // Değer gönder
}()

// Alma
value := <-ch  // Değer al (bloke eder)
fmt.Println(value)

Buffered Channels

// 3 kapasiteli buffered channel
ch := make(chan string, 3)

ch <- "birinci"
ch <- "ikinci"
ch <- "üçüncü"
// Bloke olmaz, çünkü kapasite dolmadı

Channel Yönleri

// Sadece gönderme
func sender(ch chan<- int) {
    ch <- 42
}

// Sadece alma
func receiver(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

// İki yönlü
func bidirectional(ch chan int) {
    ch <- 42
    value := <-ch
}

Select ile Çoklu Channel

ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(time.Millisecond * 100)
    ch1 <- "birinci"
}()

go func() {
    time.Sleep(time.Millisecond * 200)
    ch2 <- "ikinci"
}()

for i := 0; i < 2; i++ {
    select {
    case msg1 := <-ch1:
        fmt.Println("Channel 1:", msg1)
    case msg2 := <-ch2:
        fmt.Println("Channel 2:", msg2)
    }
}

WaitGroup ile Senkronizasyon

import "sync"

var wg sync.WaitGroup

func worker(id int) {
    defer wg.Done()
    fmt.Printf("Worker %d çalışıyor\n", id)
}

func main() {
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i)
    }
    wg.Wait()  // Tüm worker'ların bitmesini bekle
}

Best Practices

  1. Channel ownership: Sahipliği açık belirleyin
  2. Buffered channels: Duruma göre kullanın
  3. Error handling: Error’ları channel üzerinden gönderin
  4. Context: İptal işlemleri için context kullanın

Örnek: Worker Pool

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d: iş %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Worker'ları başlat
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, jobs, results, &wg)
    }

    // İşleri gönder
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Sonuçları al
    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println("Sonuç:", result)
    }
}