Introdução
- Estamos escrevendo o Dolt, o primeiro banco de dados SQL com controle de versão do mundo, na linguagem Go
- Como na maioria das bases de código em Go, usamos canais e goroutines para implementar execução concorrente
- Como programação concorrente geralmente é difícil, usamos métodos simples e intuitivos
- No entanto, herdamos de outro projeto open source um código que usa canais de uma forma muito original
var c chan chan struct{}
- Isso implementa um padrão de fan-out entre goroutines trabalhadoras, passando canais entre diferentes goroutines
- Essa abordagem era difícil de entender e complicada de manter ao considerar vazamentos de goroutines
- No fim, reescrevemos esse código e removemos o
chan chan struct{}
Por que fazer isso
- Existe uma velha piada de programação da época em que C e suas linguagens derivadas eram dominantes
- Muitas pessoas tinham dificuldade para entender ponteiros
- Como Go também é uma linguagem derivada de C, ele pode fazer a mesma coisa
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
}
- Esse código compila e imprime
i is now 100
- Em Go, é possível fazer a mesma coisa usando canais
O programador Go de 4-chans
- Vamos escrever um programa que usa 4 níveis de indireção de canais
- O canal de nível mais alto é declarado como um 4-chan
_4chan := make(chan chan chan chan int)
- O valor enviado para esse canal é um 3-chan
_3chan := make(chan chan chan int)
- Em cada nível de indireção, geramos produtores de acordo com um fator de ramificação fixo
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)
}()
}
}
- Os consumidores são tratados da mesma forma
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)
}
}
}
- Por fim, chegamos à etapa em que o valor real é enviado
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
}()
}
}()
}
}
- O consumidor soma os valores recebidos
var sum = &atomic.Int32{}
func receive(c chan int) {
for s := range c {
logrus.Debug("received int")
sum.Add(int32(s))
}
}
- Juntando tudo, executamos assim
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())
}
- Esse programa calcula a quinta potência de um número da forma mais distribuída possível
Comentários
- Há muitos motivos para não fazer isso em código real: dificuldade de implementação e depuração, questões de orgulho pessoal, críticas dos colegas etc.
- Ainda assim, é interessante porque é muito divertido e funciona
- Um dos motivos práticos é que, ao enviar canais dentro de canais, fica muito difícil fechá-los corretamente
Conclusão
- Se você tiver perguntas ou opiniões sobre padrões divertidos de concorrência em Go, pode conversar com nossa equipe e outros usuários do Dolt no Discord
Resumo do GN⁺
- Este artigo aborda um padrão de concorrência original usando canais na linguagem Go
- Embora seja ineficiente para uso em código real, é conceitualmente interessante
- Mostra como os recursos de concorrência de Go podem ser aproveitados em projetos como o Dolt
- Projetos com funcionalidade semelhante incluem PostgreSQL, MySQL etc.
1 comentários
Comentários no Hacker News
Como cientista, ao trabalhar com engenheiros de software profissionais, muita coisa que eles fazem não faz sentido para mim
Quero deixar um comentário sem muito esforço e sem substância
As velhas piadas de programação da época em que C e suas linguagens derivadas dominavam ainda continuam válidas
Isso me lembrou a música clássica do Buena Vista Social Club
Já usei o padrão
chan chan Valueouchan struct{resp chan Value}em certas situaçõesCanal de canal é um padrão comum e normalmente aparece na forma de um campo de um tipo
structque é um canaltype request struct { params, reply chan response }Um post de blog com opinião contrária sobre usar canais para implementar um mecanismo de despacho dinâmico
Isso me fez lembrar de "My favorite Erlang Program", do Joe Armstrong
Eu esperava outra coisa quando cliquei no link
Uso uma abordagem parecida em código LabVIEW para receber dados de resposta assíncrona