Giriş
- Biz, dünyanın ilk sürüm kontrollü SQL veritabanı olan Dolt'u Go diliyle yazıyoruz
- Çoğu Go kod tabanı gibi, eşzamanlı yürütmeyi kanallar ve goroutine'ler kullanarak gerçekleştiriyoruz
- Genel olarak eşzamanlı programlama zordur, bu yüzden basit ve sezgisel yöntemler kullanıyoruz
- Ancak başka bir açık kaynak projeden, kanalları oldukça yaratıcı biçimde kullanan bir kod devraldık
var c chan chan struct{}
- Bu, kanalları diğer goroutine'ler arasında aktarma yoluyla çalışan goroutine'ler arasında bir fan-out deseni uygular
- Bu yaklaşımı anlamak zordu ve goroutine sızıntılarını düşününce üzerinde çalışmak da zordu
- Sonunda bu kodu yeniden yazarak
chan chan struct{} yapısını kaldırdık
Neden böyle bir şey yapılıyor?
- C dili ve ondan türeyen dillerin baskın olduğu dönemlerden kalma eski bir programlama şakası vardır
- Birçok insan pointer'ları anlamakta zorlanırdı
- Go da C'den türeyen bir dil olduğu için aynı şeyi yapabilir
func main() {
i := 1
setInt(&i)
fmt.Printf("i is now %d", i)
}
func setInt(i *int) {
setInt2(&i)
}
func setInt2(i **int) {
setInt3(&i)
}
func setInt3(i ***int) {
setInt4(&i)
}
func setInt4(i ****int) {
****i = 100
}
- Bu kod derlenir ve
i is now 100 çıktısını verir
- Go'da kanallar kullanılarak da aynı şey yapılabilir
4-chan Go programcısı
- 4 seviyeli kanal dolaylı başvurusunu kullanan bir program yazacağız
- En üst düzey kanal 4-chan olarak tanımlanır
_4chan := make(chan chan chan chan int)
- Bu kanala gönderilen değer 3-chan'dir
_3chan := make(chan chan chan int)
- Her dolaylı başvuru seviyesinde, sabit bir dallanma faktörüne göre üreticiler oluşturulur
func sendChanChanChan(c chan chan chan chan int) {
for range factor {
go func() {
logrus.Debug("starting 3chan producer")
_3chan := make(chan chan chan int)
sendChanChan(c, _3chan)
}()
}
}
- Tüketici tarafı da aynı şekilde ele alınır
func receiveChanChanChan(c chan chan chan chan int) {
for _3chan := range c {
logrus.Debug("got message from 4chan")
for range factor {
logrus.Debug("starting 3chan consumer")
go receiveChanChan(_3chan)
}
}
}
- Sonunda gerçek değerin gönderildiği aşamaya ulaşılır
func send(_2chan chan chan int, _1chan chan int) {
_2chan <- _1chan
for range factor {
go func() {
logrus.Debug("starting int producer")
for range factor {
go func() {
logrus.Debug("sending int")
_1chan <- 1
}()
}
}()
}
}
- Tüketici, aldığı değerleri toplar
var sum = &atomic.Int32{}
func receive(c chan int) {
for s := range c {
logrus.Debug("received int")
sum.Add(int32(s))
}
}
- Her şeyi birleştirip çalıştırırız
const factor = 3
var sum = &atomic.Int32{}
func main() {
// logrus.SetLevel(logrus.DebugLevel)
_4chan := make(chan chan chan chan int)
go sendChanChanChan(_4chan)
go receiveChanChanChan(_4chan)
time.Sleep(500 * time.Millisecond)
fmt.Printf("%d ^ 5: %d", factor, sum.Load())
}
- Bu program, bir sayının 5. kuvvetini mümkün olan en dağıtık şekilde hesaplar
Yorum
- Gerçek kodda bunu yapmamak için pek çok neden vardır: uygulama ve debug zorluğu, gurur meselesi, ekip arkadaşlarının eleştirileri vb.
- Ama çok eğlenceli olması ve gerçekten çalışması açısından ilginçtir
- Pratik nedenlerden biri de, bir kanalı başka bir kanala gönderdiğinizde onu kapatmanın çok zor hale gelmesidir
Sonuç
- Go'daki eğlenceli eşzamanlılık desenleri hakkında soru veya yorumlarınız varsa, Discord'da ekibimiz ve diğer Dolt kullanıcılarıyla konuşabilirsiniz
GN⁺ özeti
- Bu yazı, Go dilinde kanallar kullanılarak kurulan yaratıcı bir eşzamanlılık desenini ele alıyor
- Gerçek kodda kullanmak için verimsiz olsa da kavramsal olarak ilginç
- Dolt gibi projelerde Go'nun eşzamanlılık özelliklerinin nasıl kullanılabileceğini gösteriyor
- Benzer işlevlere sahip projeler arasında PostgreSQL ve MySQL bulunuyor
1 yorum
Hacker News görüşleri
Bir bilim insanı olarak profesyonel yazılım mühendisleriyle çalışırken, yaptıkları şeylerin çoğu bana anlaşılmaz geliyor
Düşük çabalı ve özsüz bir yorum bırakmak istiyorum
C ve ondan türeyen dillerin hakim olduğu dönemden kalma eski programlama şakaları hâlâ geçerli
Bana Buena Vista Social Club'ın klasik müziğini hatırlatıyor
Belirli durumlarda
chan chan Valueveyachan struct{resp chan Value}kalıbını kullandımKanalın kanalı yaygın bir kalıptır; genelde struct tipindeki bir alanın kanal olması şeklinde ortaya çıkar
type request struct { params, reply chan response }gibiDinamik dispatch mekanizması uygulamak için kanalları kullanmaya karşı çıkan bir blog yazısı
Joe Armstrong'un "My favorite Erlang Program" yazısını hatırlatıyor
Bağlantıya tıkladığımda başka bir şey bekliyordum
LabVIEW kodunda asenkron yanıt verisini almak için benzer bir yöntem kullanıyorum