Einführung
- Wir schreiben Dolt, die weltweit erste SQL-Datenbank mit Versionsverwaltung, in der Programmiersprache Go
- Wie die meisten Go-Codebasen verwenden wir Channels und Goroutines, um nebenläufige Ausführung zu implementieren
- Da nebenläufige Programmierung im Allgemeinen schwierig ist, verwenden wir normalerweise einfache und intuitive Methoden
- Allerdings haben wir aus einem anderen Open-Source-Projekt Code geerbt, der Channels auf sehr originelle Weise einsetzt
var c chan chan struct{}
- Dabei werden Channels zwischen verschiedenen Goroutines weitergegeben, um ein Fan-out-Muster zwischen Worker-Goroutines zu implementieren
- Dieser Ansatz war schwer zu verstehen und unter Berücksichtigung von Goroutine-Leaks schwer zu handhaben
- Am Ende haben wir diesen Code neu geschrieben und
chan chan struct{} entfernt
Warum tut man so etwas?
- Aus der Zeit, als C und seine Ableger dominierend waren, gibt es einen alten Programmiererwitz
- Viele Menschen hatten Schwierigkeiten, Pointer zu verstehen
- Da Go ebenfalls von C abgeleitet ist, kann man dort dasselbe tun
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
}
- Dieser Code kompiliert und gibt
i is now 100 aus
- Dasselbe lässt sich in Go auch mit Channels machen
Der 4-chan-Go-Programmierer
- Wir schreiben ein Programm, das vier Ebenen von Channel-Indirektion verwendet
- Der oberste Channel wird als 4-chan deklariert
_4chan := make(chan chan chan chan int)
- Der Wert, der in diesen Channel gesendet wird, ist ein 3-chan
_3chan := make(chan chan chan int)
- Auf jeder Indirektionsebene erzeugen wir Produzenten entsprechend einem festen Verzweigungsfaktor
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)
}()
}
}
- Mit den Konsumenten verfahren wir genauso
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)
}
}
}
- Schließlich erreichen wir die Ebene, auf der die tatsächlichen Werte gesendet werden
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
}()
}
}()
}
}
- Der Konsument summiert die empfangenen Werte auf
var sum = &atomic.Int32{}
func receive(c chan int) {
for s := range c {
logrus.Debug("received int")
sum.Add(int32(s))
}
}
- Setzt man alles zusammen und führt es aus
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())
}
- Dieses Programm berechnet die fünfte Potenz einer Zahl auf möglichst verteilte Weise
Kommentar
- Es gibt viele Gründe, das in echtem Code nicht so zu machen: die Schwierigkeit bei Implementierung und Debugging, verletzter Stolz, der Tadel von Kollegen usw.
- Trotzdem ist es interessant, weil es sehr unterhaltsam ist und tatsächlich funktioniert
- Ein praktischer Grund ist, dass es sehr schwierig wird, einen Channel zu schließen, wenn man Channels durch Channels schickt
Fazit
- Wenn ihr Fragen oder Meinungen zu interessanten Nebenläufigkeitsmustern in Go habt, könnt ihr auf Discord mit unserem Team und anderen Dolt-Nutzern sprechen
Zusammenfassung von GN⁺
- Dieser Artikel behandelt ein originelles Nebenläufigkeitsmuster mit Channels in der Programmiersprache Go
- Für den Einsatz in echtem Code ist es ineffizient, aber konzeptionell interessant
- Er zeigt, wie sich die Nebenläufigkeitsfunktionen von Go in Projekten wie Dolt nutzen lassen
- Projekte mit ähnlicher Funktionalität sind unter anderem PostgreSQL und MySQL
1 Kommentare
Hacker-News-Kommentare
Als Wissenschaftler verstehe ich vieles nicht, was professionelle Softwareingenieure tun, wenn ich mit ihnen zusammenarbeite
Ich möchte einen wenig substanziellen Kommentar mit geringem Aufwand hinterlassen
Ein alter Programmierwitz aus der Zeit, als C und seine abgeleiteten Sprachen dominierten, ist immer noch gültig
Es erinnert mich an klassische Musik von Buena Vista Social Club
Ich habe das Muster „chan chan Value“ oder „chan struct{resp chan Value}“ in bestimmten Situationen verwendet
Channels von Channels sind ein gängiges Muster und erscheinen meist als Feld eines Struct-Typs, das ein Channel ist
type request struct { params, reply chan response }in dieser ArtEin Blog mit der Gegenposition, in dem Channels verwendet werden, um einen Dynamic-Dispatch-Mechanismus zu implementieren
Es erinnert an Joe Armstrongs „My favorite Erlang Program“
Als ich auf den Link geklickt habe, hatte ich etwas anderes erwartet
In LabVIEW-Code wird etwas Ähnliches verwendet, um asynchrone Antwortdaten zu empfangen