1 puan yazan GN⁺ 2024-08-29 | 1 yorum | WhatsApp'ta paylaş

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

 
GN⁺ 2024-08-29
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

    • Tek bir kod satırının 4 tane "arayüz fonksiyonu"ndan geçerek çağrıldığını gördüm
    • Her fonksiyon farklı bir dosya ve klasörde olduğu için kodu okumak çok yorucu hale geliyor
    • Birkaç adım ilerledikten sonra gerçekten hesaplama yapan kısma ulaşıp ulaşmayacağınızı sorguluyorsunuz
  • Düşük çabalı ve özsüz bir yorum bırakmak istiyorum

    • İlk birkaç paragraftaki meme, bir C programcısı olarak komiğime gitti
    • Bir dilin tuhaf varyasyonlarını görmeyi seviyorum ve bunu Go'da görmek ilginç
  • 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 Value veya chan struct{resp chan Value} kalıbını kullandım

    • Onun yerine bir mesaj veri yolu kullanabilirdim, ama o zaman da mesaj veri yolunu yönetmem gerekirdi
  • Kanalın kanalı yaygın bir kalıptır; genelde struct tipindeki bir alanın kanal olması şeklinde ortaya çıkar

    • İstek gönderirsiniz, çalışan işini bitirdikten sonra sonucu yanıt kanalına koyar
    • type request struct { params, reply chan response } gibi
    • İki kanal kullanışlı; üç veya daha fazla kanal kullandığımı hiç görmedim
  • Dinamik 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

    • Go programcısı olmadığım için şakayı hemen fark etmedim
  • LabVIEW kodunda asenkron yanıt verisini almak için benzer bir yöntem kullanıyorum

    • Yanıtı bir kuyruğa dökmek yerine, callback event channel içeren bir mesaj geçiriyorum
    • Bellek israfı var, ama tek kullanımın ardından yanıt verildiğinde kapandığı için verimli