Go veya seo terimiyle Golang, Concurrency için oldukça esnek bir dil. Esneklik yazılım dillerinde oldukça avantajlı lakin bu avantaj iyi kullanılmazsa can sıkıcı bir dezavantaj olarak bize bir bumerang gibi geri gelmekte. Neyse ki bumerang’ı rahatlıkla yönlendirmemizi sağlayacak WaitGroups gerektiğinde Go da devreye girmekte. Peki nedir bu WaitGroups?
İlk olarak Go da Concurrency ile ilgili basit bir kaç örnek yapalım.
İlk örneğimiz oldukça basit. Say diye bir fonksiyonumuz var. Bu fonksiyon ekrana ne yazdıracağını parametre olarak almakta ve 100 er milisaniye arayla ekrana basmakta.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
say("selam")
say("dünya")
}
Çıktıyı tahmin etiğinizi düşünüyorum:
selam selam selam dünya dünya dünya
Peki bir fonksiyonun önüne go yazarsak ne olur ?
Bir fonksiyonun başına go terimini eklersek artık bu fonksiyona yeni bir thread üzerinde asenkron bir şekilde arka planda çalış demiş oluruz.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("selam")
say("dünya")
}
https://go.dev/play/p/s7t6IVTccdp
Sence çıktımız ne olacak? Aşağıda çıktıyı bulabilirsin lakin ilk önce bi yere tahmini not almanı tavsiye ederim.
dünya
selam
dünya
selam
dünya
Çıktıdan anlaşılacağı üzere artık 3 kez selam basmadan hatta ilk olarak selam basmadan dünya bastık. Yani go selam yazan fonksiyonu arka plana atıp ne zaman yaparsan yap ama yap dedi diyebiliriz. Bu kod her çalıştığında farklı bir çıktıda sunabilir çünkü arka taraftaki bu işlemin ne zaman tamamlanacağı bilinmemekte. Sadece bir şekilde yapılacak er yada geç 🙂
Peki neden say fonksiyonu içerisinde uygulamamızı uyutmaktayız?
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
Gerçek dünyada buna gerek yok diye düşünebiliriz çünkü uygulamamızın ana thread ı > main i sürekli çalışmakta. Burada ise biz time.sleep fonksiyonu ile 100ms * 3 kez main i geciktirmekteyiz ki arka tarafta çalışan say(“selam”) fonksiyonu ekrana bişeyler yazabilsin. Unutmayın eğer main biterse arkaplandaki threadlardan çıkmış oluruz.
https://go.dev/play/p/s7t6IVTccdp
Go Playground ta sleep kodunu kaldırarak deneme yapabilirsiniz. Belki ekrana selam da yazabilir ama unutmayın selam yazabilmesi için main bitmeden ekrana yazdırma ( fmt.Println(s) ) tamamlanmalı.
Tüyo:
Sleep yerine ekrana 3 kez değilde 100 kez yazdır belki yardımcı olabilir 🙂
Eğer yukarıdaki mantığı anladıysanız gelelim ahiret sorularından birisine
İki fonksiyonunda başına go eklersek ne olur?
func main() {
go say("selam")
go say("dünya")
}
Evet beklediğimiz gibi hiç birşey çıkmadı 🙂 Çünkü main hemen bitti
Elbette gerçek dünyada ekrana basit bir yazıyı yazacak şeyleri arka planda çalıştırmamaktayız. Peki buna benzer bir örneğimiz var olduğunu düşünelim ve ne mutlaka bu arka tarafta çalışan fonksiyonlarımızı beklemek istersek ne olacak?
WaitGroups işte burada devreye girmekte.
ilk olarak “sync” paketi içeri aktarılır.
import (
"fmt"
"sync"
)
Sonrasında
go say fonksiyonlarımız wait group içerisine alınır.
package main
import (
"fmt"
"sync"
"time"
)
func say(s string, wg *sync.WaitGroup) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go say("selam", &wg)
go say("dünya", &wg)
wg.Wait()
}
https://go.dev/play/p/ZgFuEp0cmoO
Çıktımız ise beklenildiği üzere hepsi yazdırılır.
dünya dünya selam selam
Şimdi kodumuzu ufaktan bir modifiye edelim
package main
import (
"fmt"
"sync"
)
func say(s string, wg *sync.WaitGroup) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}
func sayWithGo(s string, wg *sync.WaitGroup) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go sayWithGo("selam", &wg)
wg.Wait()
go say("dünya", &wg)
}
Buradaki kodumuzun çıktısı ise sadece
selam selam
olacak. Bunun sebebi ise biz bir adet arkada çalışan taskı beklemekteyiz wg.Add(1) ile. Eğer burada 1 yerine 2 yazsaydık => wg.Add(2)
selam selam fatal error: all goroutines are asleep - deadlock!
Çıktısını alırdık çünkü wg.Add(2) iler iki adet arka plan taskımız olduğunu söyledik sonrasında ise sadece go sayWithGo(“selam”, &wg) foksiyonunu arka plana gönderdik ve wg.Wait() ile bekle dedik. Tamam birincisi arkada çalıştı ve bitti ama 2. yi hiç başlatmadık bu yüzde go bekledi durdu bir ömür boyu buda Deadlock a sebep oldu
Eğer kodumuzu şu şekilde güncellersek ne olur ?
package main
import (
"fmt"
"sync"
)
func say(s string, wg *sync.WaitGroup) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}
func sayWithGo(s string, wg *sync.WaitGroup) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go sayWithGo("selam", &wg)
go say("dünya", &wg)
wg.Wait()
}
Bu sefer yine deadlock alırız.
dünya dünya selam selam fatal error: all goroutines are asleep - deadlock!
Bu durumun sebebi ise WaitGroup counterımızın 2 olarak atamıştık wg.Add(2) ile sonrasında go sayWithGo metodu bitince son aşamadaki wg.Done() ile 2 yi 1 olarak güncelledik. Ama wg.Wait() in sonsuz döngüden çıkması için waitGroup sayacının 0 olması gerek. Bizim say de wg.Done() malesef yok buda deadlockların efendisini çağırdı 🙂
TLDR
go fonksiyon()
arkaplanda bu işlemi yap demektir.
var wg sync.WaitGroup
wg isminde bir WaitGroup oluşturur.
wg.Add(2)
Bizim arka planda iki adet işlemimiz olacak demektir.
wg.Wait()
Arka plandaki işlemler tamamlanana kadar bekletir. Burada WaitGroup sayacı sıfırlanıncaya kadar beklenir
wg.Done()
Wait group sayacını bir azaltır
Kaynaklar:
https://go.dev/tour/concurrency/1
https://gobyexample.com/waitgroups
https://www.reply.com/alpha-reply/en/content/go-concurrency-with-waitgroup
Bir yanıt yazın