1 poin oleh GN⁺ 2024-08-29 | 1 komentar | Bagikan ke WhatsApp

Pengantar

  • Kami menulis Dolt, database SQL dengan version control pertama di dunia, dalam bahasa Go
  • Seperti kebanyakan codebase Go, kami menggunakan channel dan goroutine untuk mengimplementasikan eksekusi konkuren
  • Secara umum, pemrograman konkuren itu sulit, jadi kami biasanya memakai cara yang sederhana dan intuitif
  • Namun, kami mewarisi kode dari proyek open source lain yang menggunakan channel dengan cara yang sangat kreatif
var c chan chan struct{}
  • Ini adalah cara meneruskan channel antar goroutine lain, untuk mengimplementasikan pola fan-out di antara goroutine pekerja
  • Pendekatan ini sulit dipahami, dan juga sulit dikerjakan jika mempertimbangkan kebocoran goroutine
  • Pada akhirnya kami menulis ulang kode ini dan menghapus chan chan struct{}

Mengapa melakukan ini

  • Ada lelucon pemrograman lama dari masa ketika bahasa C dan turunannya mendominasi
  • Banyak orang kesulitan memahami pointer
  • Karena Go juga merupakan bahasa turunan C, hal yang sama bisa dilakukan
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
}
  • Kode ini dapat dikompilasi dan mencetak i is now 100
  • Di Go, hal yang sama juga bisa dilakukan dengan memakai channel

Programmer Go 4-chan

  • Kita akan menulis program yang menggunakan 4 tingkat indireksi channel
  • Channel tingkat teratas dideklarasikan sebagai 4-chan
_4chan := make(chan chan chan chan int)
  • Nilai yang dikirim ke channel ini adalah 3-chan
_3chan := make(chan chan chan int)
  • Pada setiap tingkat indireksi, kita membuat producer sesuai faktor percabangan tertentu
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)
    }()
  }
}
  • Consumer juga diproses dengan cara yang sama
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)
    }
  }
}
  • Akhirnya kita mencapai tahap pengiriman nilai yang sebenarnya
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
        }()
      }
    }()
  }
}
  • Consumer menjumlahkan nilai yang diterima
var sum = &atomic.Int32{}

func receive(c chan int) {
  for s := range c {
    logrus.Debug("received int")
    sum.Add(int32(s))
  }
}
  • Lalu semuanya digabungkan dan dijalankan
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())
}
  • Program ini menghitung pangkat lima dari sebuah angka dengan cara yang semaksimal mungkin terdistribusi

Komentar

  • Ada banyak alasan untuk tidak melakukan ini di kode nyata: sulit diimplementasikan dan di-debug, melukai harga diri, serta mengundang cibiran rekan kerja
  • Namun tetap menarik karena sangat lucu dan benar-benar bekerja
  • Salah satu alasan praktisnya adalah bahwa ketika channel dikirim sebagai channel, penutupannya menjadi sangat sulit

Kesimpulan

  • Jika Anda punya pertanyaan atau pendapat tentang pola konkurensi Go yang menarik, Anda bisa berbicara dengan tim kami dan pengguna Dolt lainnya di Discord

Ringkasan GN⁺

  • Artikel ini membahas pola konkurensi yang kreatif di bahasa Go dengan menggunakan channel
  • Meskipun tidak efisien untuk dipakai di kode nyata, idenya menarik secara konseptual
  • Artikel ini menunjukkan bagaimana fitur konkurensi Go dapat dimanfaatkan dalam proyek seperti Dolt
  • Proyek dengan fungsi serupa antara lain PostgreSQL dan MySQL

1 komentar

 
GN⁺ 2024-08-29
Pendapat Hacker News
  • Sebagai ilmuwan, saat bekerja dengan insinyur perangkat lunak profesional, banyak hal yang mereka lakukan terasa tidak bisa dipahami

    • Pernah melihat satu baris kode dipanggil melalui 4 "fungsi antarmuka"
    • Setiap fungsi berada di file dan folder yang berbeda, sehingga membaca kodenya menjadi sangat melelahkan
    • Setelah masuk beberapa tahap, muncul pertanyaan apakah akan benar-benar sampai ke bagian yang melakukan perhitungan
  • Ingin meninggalkan komentar bernilai rendah dengan usaha minim

    • Meme di beberapa paragraf pertama lucu dari sudut pandang sebagai programmer C
    • Suka melihat variasi aneh suatu bahasa, dan menarik melihatnya di Go
  • Lelucon pemrograman lama dari masa ketika C dan bahasa turunannya mendominasi masih tetap berlaku

  • Mengingatkan pada musik klasik dari Buena Vista Social Club

  • Pernah memakai pola "chan chan Value" atau "chan struct{resp chan Value}" dalam situasi tertentu

    • Bisa saja memakai message bus sebagai gantinya, tetapi kemudian harus menangani message bus itu sendiri
  • Channel of channels adalah pola yang umum, dan biasanya muncul sebagai field bertipe channel di dalam struct

    • Caranya dengan mengirim permintaan, lalu pekerja menaruh hasil ke channel respons setelah tugas selesai
    • Bentuknya seperti type request struct { params, reply chan response }
    • Dua channel berguna, dan belum pernah melihat lebih dari tiga channel
  • Blog yang berpendapat sebaliknya tentang penggunaan channel untuk mengimplementasikan mekanisme dynamic dispatch

    • Digunakan di bahasa Limbo, dengan konsep yang sama seperti Go
    • Tautan blog
  • Mengingatkan pada "My favorite Erlang Program" milik Joe Armstrong

  • Saat mengklik tautannya, sempat mengharapkan sesuatu yang lain

    • Karena bukan programmer Go, tidak langsung menangkap leluconnya
  • Di kode LabVIEW, pernah memakai cara serupa untuk menerima data respons asinkron

    • Alih-alih membuang respons ke queue, pesannya diteruskan dengan menyertakan callback event channel
    • Boros memori, tetapi efisien karena ditutup saat respons setelah sekali pakai