Goroutine yaşam döngüsü


Bir goroutine icerisinde baska bir goroutine baslatildiginda, ilk goroutine islemini bitirse veya crash olsa bile sonradan baslatilan goroutine bundan etkilenmez ve calismaya devam eder.

Bu yazida, baslatilan bir goroutine’e nasil mudahele ederiz, goroutine’ler arasinda bir iliski kurup biri sonlandiginda digerininde sonlanmasini nasil saglariz bir ornek uzerinden anlatmaya calisacagim.


Asagidaki ornekte bir goroutine baslatiyorum(GR1) ve bu goroutine icerisinde baska bir goroutine baslatiyorum(GR2) ve sonrasinda GR1 goroutininde panic atiyorum lakin GR2 calismaya devam ediyor. Go Playground Linki‘nden kendiniz calistirip gorebilirsiniz.


import (
    "fmt"
    "time"
)

func main() {
    go A() // GR1

    time.Sleep(10 * time.Second)
    fmt.Println("Program kapandi")
}

func myRecover() {
    if r := recover(); r != nil {
        fmt.Println("Panic recovered: ", r)
    }
}

func A() {
    defer myRecover()
    go B()  // GR2

    nums := []int{1, 2, 3}
    for _, v := range nums {
        fmt.Println("GR1", v)
        time.Sleep(1 * time.Second)
    }
    panic("A fonksiyonu patladi")
}

func B() {
    names := []string{"Ahmet", "Mehmet", "Ali", "Veli", "Osman"}
    for _, v := range names {
        fmt.Println("GR2", v)
        time.Sleep(2 * time.Second)
    }
    fmt.Println("B fonksiyonu bitti")
}

Yukaridaki kodu calistirdiginizda goreceksinizki GR1 ve GR2 birbirinden tamamen bagimsiz calisiyorlar, GR1 crash oldugu halde GR2 hala calismaya devam ediyor.

GR1 kapandiginda GR2‘ninde kapanmasi saglamak icin 2 yontem mevcut. Bunlardan biri context diger channel kullanmak.

1- Context cancellation

Yukaridaki ornekten gidersek GR2 baskatilirken fonksiyona parametre olarak bir context verip, bu context sayesinde GR1 bittiginde GR2‘ninde kapatilmasi saglayabiliyoruz.

func A() {
    defer myRecover()
    // Bir context olusturuyoruz.
    ctx := context.Background()
    // Bu context'i kullanarak cancel edilebilir yeni bir context olusturuyoruz.
    ctx, cancel := context.WithCancel(ctx)

    // A fonksiyonundan cikarken cancel fonksiyonu cagrilacak
    // O'da arka planda context'in Done channel'ina sinyal gonderecek
    // Ve biz bu sinyali dinleyerek B fonksiyonunun durmasini saglayacagiz.
    defer cancel() 
    go B(ctx) // GR2

    // ....
}

func B(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("GR2: Context cancel oldu")
            return
        default:
            names := []string{"Ahmet", "Mehmet", "Ali", "Veli", "Osman"}
            for _, v := range names {
                fmt.Println("GR2", v)
                time.Sleep(1 * time.Second)
            }
        }
    }
}

Go Playground Linki‘nden calistirdiginizda goreceksiniz ki GR1 kapandiktan biraz sonra GR2‘de kapaniyor.

Calistirdiginizda farkettiyseniz panic yedikten sonra GR2 hemen kapanmiyor. Bunun sebebi su: B fonksiyonu calismaya basladiginda ilk olarak default case’ine dusuyor, ve bu case icerisindeki kod calismaya devam ederken ctx.Done() channelindan sinyal geliyor lakin default case’i tamamen bitmeden <-ctx.Done() case’i calismiyor, cunku B fonksiyonunun icerisi async degil. default case’i bittiginde select statement‘i iceren for dongusu tekrar iterate oldugunda bu sefer <-ctx.Done() case’ine dusuyor ve return ettigi icin fonksiyondan tamamen cikiliyor ve goroutine kapanmis oluyor.

2- Channellar


func A() {
    q := make(chan bool)
    defer func(){
        // A fonksiyonundan cikarken q channel'ina data basiyoruz.
        q <- true
    }()
    defer myRecover()
    go B(q) // GR2
    
    // ....
}
func B(q chan bool) {
    for {
        select {
        case <-q:
            fmt.Println("GR2: Context cancel oldu")
            return
        default:
            // ...
        }
    }
}

Go Playground Linki

Bu ornekte ise B fonksiyonunda parametre olarak bir channel veriyoruz ve A bittiginde bu channel’a sinyal gonderiyoruz, B ise bu channel’dan sinyal geldiginde islemi bitiriyor ve return ederek goroutine’nin kapanmasini saglamis oluyor.

Load comments