1 points par GN⁺ 2024-08-29 | 1 commentaires | Partager sur WhatsApp

Introduction

  • Nous écrivons Dolt, la première base de données SQL avec gestion de versions au monde, en langage Go
  • Comme la plupart des codebases Go, nous utilisons des canaux et des goroutines pour implémenter l’exécution concurrente
  • En règle générale, la programmation concurrente est difficile, donc nous utilisons des méthodes simples et intuitives
  • Cependant, nous avons hérité dans un autre projet open source d’un code qui utilise les canaux d’une manière très originale
var c chan chan struct{}
  • Cela consiste à faire passer des canaux entre différentes goroutines afin d’implémenter un motif de fan-out entre goroutines de travail
  • Cette approche était difficile à comprendre et compliquée à manipuler lorsqu’on tient compte des fuites de goroutines
  • Nous avons finalement réécrit ce code pour éliminer chan chan struct{}

Pourquoi faire ça

  • Il existe une vieille blague de programmation qui remonte à l’époque où le C et ses langages dérivés dominaient
  • Beaucoup de gens avaient du mal à comprendre les pointeurs
  • Comme Go est lui aussi un langage dérivé du C, il peut faire la même chose
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
}
  • Ce code compile et affiche i is now 100
  • On peut faire la même chose en Go avec des canaux

Le programmeur Go 4-chan

  • Nous allons écrire un programme qui utilise 4 niveaux d’indirection de canaux
  • Le canal de niveau supérieur est déclaré comme un 4-chan
_4chan := make(chan chan chan chan int)
  • La valeur envoyée dans ce canal est un 3-chan
_3chan := make(chan chan chan int)
  • À chaque niveau d’indirection, nous créons des producteurs selon un facteur de branchement constant
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)
    }()
  }
}
  • Les consommateurs sont traités de la même manière
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)
    }
  }
}
  • Finalement, on atteint l’étape où les vraies valeurs sont envoyées
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
        }()
      }
    }()
  }
}
  • Le consommateur additionne les valeurs reçues
var sum = &atomic.Int32{}

func receive(c chan int) {
  for s := range c {
    logrus.Debug("received int")
    sum.Add(int32(s))
  }
}
  • En assemblant le tout, on l’exécute
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())
}
  • Ce programme calcule la puissance 5 d’un nombre de la manière la plus distribuée possible

Commentaire

  • Il existe de nombreuses raisons de ne pas faire cela dans du vrai code : difficulté d’implémentation et de débogage, question d’ego, reproches des collègues, etc.
  • Mais c’est intéressant parce que c’est très amusant et que ça fonctionne
  • L’une des raisons pratiques est qu’envoyer un canal dans un autre canal rend sa fermeture très difficile

Conclusion

  • Si vous avez des questions ou des remarques sur des motifs de concurrence amusants en Go, vous pouvez discuter avec notre équipe et d’autres utilisateurs de Dolt sur Discord

Résumé GN⁺

  • Cet article traite d’un motif de concurrence original utilisant des canaux en langage Go
  • Son usage dans du vrai code est inefficace, mais le concept est intéressant
  • Il montre comment les fonctionnalités de concurrence de Go peuvent être utilisées dans un projet comme Dolt
  • Parmi les projets offrant des fonctionnalités similaires, on trouve PostgreSQL, MySQL, etc.

1 commentaires

 
GN⁺ 2024-08-29
Avis Hacker News
  • En tant que scientifique, quand je travaille avec des ingénieurs logiciels professionnels, une grande partie de ce qu’ils font m’échappe

    • J’ai déjà vu une ligne de code être appelée en passant par 4 « fonctions d’interface »
    • Chaque fonction se trouve dans un fichier et un dossier différents, ce qui rend la lecture du code très fatigante
    • Au bout de quelques niveaux, on se demande quand on finira par atteindre la partie qui fait réellement les calculs
  • J’ai envie de laisser un commentaire peu substantiel et de faible effort

    • Le mème des premiers paragraphes m’a fait rire en tant que programmeur C
    • J’aime voir des variantes étranges d’un langage, et c’est intéressant de voir ça en Go
  • Une vieille blague de programmation datant de l’époque où C et ses langages dérivés dominaient reste toujours valable

  • Ça rappelle la musique classique du Buena Vista Social Club

  • J’ai déjà utilisé le motif chan chan Value ou chan struct{resp chan Value} dans certaines situations

    • On aurait pu utiliser un bus de messages à la place, mais on se retrouve alors à devoir gérer le bus de messages
  • Les canaux de canaux sont un motif courant, qui apparaît généralement sous la forme d’un champ de type canal dans une structure

    • On envoie une requête, puis le worker place le résultat dans le canal de réponse une fois le travail terminé
    • Une forme comme type request struct { params, reply chan response }
    • Deux canaux sont utiles, et je n’en ai jamais vu plus de trois
  • Un billet de blog opposé à l’utilisation des canaux pour implémenter un mécanisme de dispatch dynamique

    • Utilisé dans le langage Limbo, avec le même concept qu’en Go
    • Lien du blog
  • Ça fait penser à « My favorite Erlang Program » de Joe Armstrong

  • En cliquant sur le lien, je m’attendais à autre chose

    • Je ne suis pas programmeur Go, donc je n’ai pas compris la blague tout de suite
  • Utilisation d’une approche similaire dans du code LabVIEW pour recevoir des données de réponse asynchrones

    • Au lieu de déverser les réponses dans une file d’attente, on transmet un message qui inclut un canal d’événement de callback
    • C’est un gaspillage de mémoire, mais comme c’est fermé après une seule utilisation lors de la réponse, ça reste efficace