Go ve Concurrency
ud-204
Burada Effective Go’nun concurrency ile ilgili kısmına biraz bakalım:
https://go.dev/doc/effective_go#concurrency
Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables.
Bildiğimiz diğer dillerde, Python, Java, C vs bir shared variable a “doğru” erişimi sağlayan modelleri yapmak zor olduğu için concurrent programlama da zor oluyor. Mesela aşağıdaki duruma bakalım, oldukça tipik bir durum.
Ref: https://github.com/ardanlabs/gotraining/blob/master/topics/go/concurrency/data_race/README.md
Burada tipik bir race condition görüyoruz. İki farklı thread’ten aynı değişkene, aynı bellek alanına beraber erişiliyor. Her iki thread de değişkeni bir arttırmaya çalışıyor. Ama işletim sisteminin çizelgeleyicisinin tercihinden dolayı burada bulunan race condition açığa çıkıyor ve değişken günün sonunda 4 olmalı iken 2 değerinde oluyor.
Go’da önerilen temel yaklaşım ilerleyen kısımlarda kullanacağımız channel yaklaşımıdır. Bu modelde, yukarıdaki görselde olduğu gibi race condition’ların olmayacağı by-design garanti ediliyor. Burada birden fazla thread tarafından, aslında goroutine, aynı anda bir bellek alanına erişim olmadığı söyleniyor. O yüzden diyorlar ki
Tüyo
Do not communicate by sharing memory; instead, share memory by communicating.
Yani dedikleri threadler arası iletişimi paylaşımlı bellek ile yapmayın, belleği iletişim kurarak paylaşın.
Dilde ileride göreceğimiz mutex ve atomic gibi kavramlar var ama önerilen channel kullanımı.
Tek CPU üzerinde tek thread ile bir program çalışıyorsa senkronizasyona ihtiyaç yok. Aynı anda bu programlardan iki tane çalışırsa da gerek yok. Ama ne zaman bu iki program birbirine veri göndermeye başlayacak işte o zaman ihtiyaç oluyor. Eğer iletişim synchronization primitive leri üzerinden oluyrsa gene başka senkronizasyon araçlarına ihtiyaç yok, kendisi zaten düzgün çalışacak. Unix Pipeline’ları buna güzel bir örnek.
Not
Go, Hoare’nin CSP çalışmasını baz alır. Unix Pipe’lar buna benziyor(muş).
Goroutines
Goroutine’ler aynı adres space’te diğer goroutine’ler ile çalışır. Maaliyeti düşüktür. İşletim sistemlerinin thread’leri üzerinde ihtiyaca göre Go runtime tarafından multiplex edilerek kullanılır. Yani her goroutine bir gerçek thread olmak zorunda değildir.
Bir fonksiyon ya da metod çağrısının başına go yazdığımız zaman goroutine
oluşturmuş oluruz. Linux’ta bir işi command & ile arkaya atmaya
benzetilebilir.
Function literal yani anoymous function kullanışlı olabilir.
func Announce(message string, delay time.Duration) {
go func() {
time.Sleep(delay)
fmt.Println(message)
}() // Note the parentheses - must call the function.
}
Tabii burada goroutine’lerin işinin bitip bitmediğini anlamak pek mümkün değil. Bunu için channel kullanabiliriz. Örnek (ileride göreceğiz):
package main
import "fmt"
func yap(x int) int {
return x * 5
}
func main() {
ch := make(chan int)
go func() {
ch <- yap(3)
}()
fmt.Println(<-ch) //15
}
go ile bir fonksiyon çağırırsak ve bunun dönüş değeri varsa bunlar discard
edilir. Onun için channel vs kullanmak gerekir geri dönüş değerini almak
için.