Golang Pro: rede, multithreading, estruturas de dados e aprendizado de máquina com Go

imagem Olá Habitantes!



Você já está familiarizado com os conceitos básicos da linguagem Go? Então este livro é para você. Michalis Tsukalos irá demonstrar as capacidades da linguagem, dar explicações claras e simples, dar exemplos e sugerir padrões de programação eficazes. Conforme você aprende as nuances do Go, você vai dominar os tipos de dados e estruturas da linguagem, bem como empacotamento, simultaneidade, programação de rede, design de compilador, otimização e muito mais. Os materiais e exercícios no final de cada capítulo ajudarão a reforçar seus novos conhecimentos. Um material exclusivo será o capítulo sobre aprendizado de máquina em Go, que o conduzirá desde técnicas estatísticas básicas até regressão e clustering. Você aprenderá técnicas de classificação, redes neurais e detecção de anomalias. Nas seções aplicadas, você aprenderá a usar Go com Docker e Kubernetes, Git, WebAssembly, JSON e muito mais.



Sobre o que é esse livro
1 «Go » Go , godoc , Go-. , . , Go .



2 «Go » Go . unsafe, , Go- C, C- — Go.



, defer, strace(1) dtrace(1). , Go, Go WebAssembly.



3 « Go» , Go: , -, , , , . !



4 « » Go struct, , , , . , switch, strings math/big, Go « — » XML JSON.



5 « Go » , Go . , , -, , . Go container, , Go .



6 « Go» , init(), Go- syscall, text/template html/template. , , go/scanner, go/parser go/token. Go!



7 « » Go: , . , - Go Go- Delve.



8 « UNIX-, » Go. , flag , UNIX, , bytes, io.Reader io.Writer, Viper Cobra Go. : Go, Go Systems Programming!



9 « Go: , » , — , Go.



, , , sync Go.



10 « Go: » . , ! Go, select, Go, , , sync.Mutex sync.RWMutex. context, , (race conditions).



11 «, » , , - , , Go-, Go-.



12 « Go» net/http , - - Go. http.Response, http.Request http.Transport, http.NewServeMux. , Go -! , Go DNS-, Go gRPC.



13 « : » HTTPS- Go UDP TCP net. , RPC, Go TCP- «» .



14 « Go» Go , , , , , TensorFlow, Go Apache Kafka.



. Go, , Go-, Go-, Go C WebAssembly, Go. 5 6 7. Go- , Go- Go.



Go. 8–11 Go, Go, , . Go.

Go WebAssembly, Docker Go, Viper Cobra, JSON YAML, , , go/scanner go/token, git(1) GitHub, atomic, Go gRPC HTTPS.



, Go-, . : -, , , -, .



E novamente sobre os canais Go



Depois que a palavra-chave select é usada, há várias maneiras exclusivas de usar os tubos Go que fazem muito mais do que o que você viu no Capítulo 9. Nesta seção, você aprenderá sobre as diferentes maneiras de usar os tubos Go.



Deixe-me lembrá-lo de que o valor zero para canais é nulo, e se você enviar uma mensagem para um canal fechado, o programa entrará em modo de pânico. Mas se você tentar ler dados de um canal fechado, obterá um valor zero para este tipo de canal. Assim, após fechar o canal, você não pode mais gravar dados nele, mas ainda pode lê-lo.



Para que um canal seja fechado, ele não precisa ser projetado para receber apenas dados. Além disso, o canal zero está sempre bloqueado, ou seja, uma tentativa de leitura ou escrita do canal zero bloqueará o canal. Esta propriedade de canais é muito útil quando você deseja desabilitar uma ramificação de uma instrução select definindo a variável de canal como nil.



Por fim, ao tentar fechar o canal zero, o programa gerará pânico. Vejamos o exemplo closeNilChannel.go:



package main

func main() {
      var c chan string
      close(c)
}


Executar closeNilChannel.go produzirá o seguinte resultado:



$ go run closeNilChannel.go
panic: close of nil channel
goroutine 1 [running]:
main.main()
       /Users/mtsouk/closeNilChannel.go:5 +0x2a
exit status 2


Canais de sinal



Um canal de sinalização é um canal usado apenas para sinalização. Simplificando, o canal de sinalização pode ser usado quando você deseja informar outro programa sobre algo. Os canais de sinalização não precisam ser usados ​​para transferir dados.



Os canais de sinalização não devem ser confundidos com o tratamento de sinais do UNIX discutido no Capítulo 8, eles são coisas completamente diferentes.


Um exemplo de código que usa canais de sinalização é discutido posteriormente neste capítulo.



Canais com buffer



O tópico desta subseção é tubos em buffer. Esses são canais que permitem ao agendador Go enfileirar rapidamente os trabalhos para lidar com mais solicitações. Além disso, eles podem ser usados ​​como semáforos para limitar a largura de banda de um aplicativo.



O método apresentado aqui funciona assim: todas as solicitações recebidas são redirecionadas para um canal, que as processa por sua vez. Quando o canal termina de processar a solicitação, ele envia uma mensagem ao chamador original de que o canal está pronto para processar uma nova solicitação. Portanto, a capacidade do buffer de um canal limita o número de solicitações simultâneas que o canal pode armazenar.



Veremos esse método usando o código do programa bufChannel.go como exemplo. Vamos dividir em quatro partes.



A primeira parte do código bufChannel.go se parece com isto:



package main

import (
       "fmt"
)


A segunda parte do arquivo bufChannel.go contém o seguinte código Go:



func main() {
      numbers := make(chan int, 5)
      counter := 10


A definição de números apresentada aqui permite que você armazene até cinco inteiros neste tubo.



A terceira parte do bufChannel.go contém o seguinte código Go:



for i := 0; i < counter; i++ {
     select {
     case numbers <- i:
     default:
            fmt.Println("Not enough space for", i)
     }
}


Neste código, tentamos colocar dez números no canal de números. No entanto, como os números só têm espaço para cinco inteiros, não podemos armazenar todos os dez inteiros nele.



O resto do código Go de bufChannel.go é assim:



   for i := 0; i < counter+5; i++ {
        select {
              case num := <-numbers:
                    fmt.Println(num)
              default:
                    fmt.Println("Nothing more to be done!")
              break
        }
   }
}


Neste código Go, tentamos ler o conteúdo do canal de números usando um loop for e uma instrução select. Enquanto houver algo para ler no canal de números, o primeiro ramo da instrução select será executado. Quando o canal de números está vazio, o desvio padrão é executado.



Executar bufChannel.go produzirá o seguinte resultado:



$ go run bufChannel.go
Not enough space for 5
Not enough space for 6
Not enough space for 7
Not enough space for 8
Not enough space for 9
0
1
2
3
4
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!


Canais zero



Nesta seção, você aprenderá sobre o canal zero. Este é um tipo especial de canal que está sempre bloqueado. Veremos esses canais usando o programa nilChannel.go como exemplo. Vamos dividi-lo em quatro partes de código.



A primeira parte de nilChannel.go é assim:



package main

import (
       "fmt"
       "math/rand"
       "time"
)


A segunda parte de nilChannel.go contém o seguinte código Go:



func add(c chan int) {
      sum := 0
      t := time.NewTimer(time.Second)

      for {
           select {
           case input := <-c:
                 sum = sum + input
           case <-t.C:
                 c = nil
                 fmt.Println(sum)
           }
      }
}


Aqui, usando a função add () como exemplo, mostra como o canal zero é usado. O operador <-tC bloqueia o canal C do temporizador t pelo tempo especificado na chamada para time.NewTimer (). Não confunda o canal c, que é um argumento de função, com o canal tC, que pertence ao temporizador t. Quando o tempo expira, o temporizador envia um valor para o canal tC, que inicia a execução da ramificação correspondente da instrução select - ele define o canal c como nulo e exibe o valor da variável sum.



O terceiro snippet de código nilChannel.go tem a seguinte aparência:



func send(c chan int) {
      for {
           c <- rand.Intn(10)
      }
}


O objetivo da função send () é gerar números aleatórios e enviá-los ao canal, desde que o canal esteja aberto.



O resto do código Go em nilChannel.go é assim:



func main() {
      c := make(chan int)
      go add(c)
      go send(c)
      time.Sleep(3 * time.Second)
}


A função time.Sleep () é necessária para que os dois goroutines tenham tempo suficiente para executar.



Executar nilChannel.go produzirá os seguintes resultados:



$ go run nilChannel.go
13167523
$ go run nilChannel.go
12988362


Como o número de vezes que o primeiro branch da instrução select em add () é executado não é fixo, executar nilChannel.go várias vezes produzirá resultados diferentes.



Canais de canal



Um canal de canal é um tipo especial de variável de canal que funciona com outros canais em vez dos tipos de variáveis ​​usuais. No entanto, você ainda precisa declarar um tipo de dados para um canal de canais. Para definir o canal dos canais, use a palavra-chave chan duas vezes seguidas, conforme mostrado na seguinte declaração:



c1 := make(chan chan int)


Outros tipos de canais apresentados neste capítulo são mais populares e úteis do que os canais de canais.


Examinaremos o uso de canais de canal usando o código de exemplo encontrado no arquivo chSquare.go. Vamos dividir em quatro partes.



A primeira parte de chSquare.go é assim:



package main

import (
       "fmt"
       "os"
       "strconv"
       "time"
)

var times int


A segunda parte de chSquare.go contém o seguinte código Go:



func f1(cc chan chan int, f chan bool) {
      c := make(chan int)
      cc <- c
      defer close(c)

      sum := 0
      select {
      case x := <-c:
            for i := 0; i <= x; i++ {
                 sum = sum + i
            }
            c <- sum
      case <-f:
            return
      }
}


Tendo declarado um canal regular do tipo int, nós o passamos para a variável channel channel. Então, usando a instrução select, temos a oportunidade de ler os dados de um canal int regular ou sair da função usando o canal de sinal f.



Depois de ler um valor do canal c, executamos um loop for que calcula a soma de todos os inteiros de 0 ao valor inteiro que acabamos de ler. Em seguida, enviamos o valor calculado para o canal interno c, e é isso.



A terceira parte de chSquare.go contém o seguinte código Go:



func main() {
      arguments := os.Args
      if len(arguments) != 2 {
          fmt.Println("Need just one integer argument!")
          return
      }
      times, err := strconv.Atoi(arguments[1])
      if err != nil {
           fmt.Println(err)
           return
      }

      cc := make(chan chan int)


Na última linha deste trecho de código, estamos declarando uma variável de canal chamada cc. Esta variável é a estrela deste programa, pois tudo depende dela. A variável cc é passada para f1 () e usada no próximo loop for.



O resto do código chSquare.go Go tem a seguinte aparência:



   for i := 1; i < times+1; i++ {
        f := make(chan bool)
        go f1(cc, f)
        ch := <-cc
        ch <- i
        for sum := range ch {
             fmt.Print("Sum(", i, ")=", sum)
        }
        fmt.Println()
        time.Sleep(time.Second)
        close(f)
    }
}


O canal f é o canal de sinal para o final da goroutine quando todo o trabalho estiver concluído. A instrução ch: = <-cc permite que você obtenha um canal regular de uma variável de canal para passar um valor int usando o operador ch <- i. Depois disso, lemos os dados do tubo usando um loop for. A função f1 () é programada para retornar um valor, mas também podemos ler vários valores. Observe que cada valor de i é servido por sua própria goroutine.



O tipo de canal de sinal pode ser qualquer coisa, incluindo o bool usado no código anterior e a estrutura {}, que será usada para o canal de sinal na próxima seção. A principal vantagem de um canal de sinalização do tipo struct {} é que os dados não podem ser enviados para esse canal, o que impede a ocorrência de erros.



Executar chSquare.go produzirá resultados como este:



$ go run chSquare.go 4
Sum(1)=1
Sum(2)=3
Sum(3)=6
Sum(4)=10
$ go run chSquare.go 7
Sum(1)=1
Sum(2)=3
Sum(3)=6
Sum(4)=10
Sum(5)=15
Sum(6)=21
Sum(7)=28


Escolhendo a sequência de execução de goroutines



Você não é obrigado a fazer suposições sobre a sequência de execução dos goroutines. Porém, há momentos em que é necessário controlar esse pedido. Nesta subseção, você aprenderá como fazer isso usando canais de sinalização.



Você pode perguntar: "Por que criar goroutines e executá-los em uma determinada ordem quando é muito mais fácil fazer o mesmo com funções regulares?" A resposta é simples: as goroutines podem ser executadas simultaneamente e esperar que outras goroutines sejam concluídas, enquanto as funções não podem, porque são executadas sequencialmente.


Nesta subseção, veremos um programa Go chamado defineOrder.go. Vamos dividir em cinco partes. A primeira parte de defineOrder.go se parece com isto:



package main

import (
       "fmt"
       "time"
)

func A(a, b chan struct{}) {
      <-a
      fmt.Println("A()!")
      time.Sleep(time.Second)
      close(b)
}


A função A () está bloqueada pelo canal armazenado no parâmetro a. Assim que este canal for desbloqueado em main (), a função A () começará a funcionar. Finalmente, ele fechará o canal b, desbloqueando assim outra função - neste caso, B ().



A segunda parte de defineOrder.go contém o seguinte código Go:



func B(a, b chan struct{}) {
      <-a
      fmt.Println("B()!")
      close(b)
}


A lógica da função B () é a mesma de A (). Esta função é bloqueada até que o canal a seja fechado. Em seguida, ele faz seu trabalho e fecha o canal b. Observe que os canais aeb referem-se aos nomes dos parâmetros da função.



A terceira parte do código para defineOrder.go se parece com isto:



func C(a chan struct{}) {
      <-a
      fmt.Println("C()!")
}


A função C () está bloqueada e aguarda o fechamento do canal a antes de iniciar.



A quarta parte de defineOrder.go contém o seguinte código:



func main() {
      x := make(chan struct{})
      y := make(chan struct{})
      z := make(chan struct{})


Esses três canais se tornarão parâmetros para três funções.



O último snippet de defineOrder.go contém o seguinte código Go:



     go C(z)
     go A(x, y)
     go C(z)
     go B(y, z)
     go C(z)

     close(x)
     time.Sleep(3 * time.Second)
}


Aqui, o programa executa todas as funções necessárias e, em seguida, fecha o canal xe dorme por três segundos.



Executar defineOrder.go produzirá o resultado desejado, embora a função C () seja chamada várias vezes:



$ go run defineOrder.go
A()!
B()!
C()!
C()!
C()!


Chamar C () várias vezes como uma goroutina não causará problemas, porque C () não fecha nenhum canal. Mas se você chamar A () ou B () mais de uma vez, provavelmente, uma mensagem de erro será exibida, por exemplo:



$ go run defineOrder.go
A()!
A()!
B()!
C()!
C()!
C()!
panic: close of closed channel
goroutine 7 [running]:
main.A(0xc420072060, 0xc4200720c0)
       /Users/mtsouk/Desktop/defineOrder.go:12 +0x9d
created by main.main
       /Users/mtsouk/Desktop/defineOrder.go:33 +0xfa
exit status 2


Como você pode ver, aqui a função A () foi chamada duas vezes. No entanto, quando A () fecha um canal, um de seus goroutines detecta que o canal já está fechado e cria uma situação de pânico ao tentar fechá-lo novamente. Se tentarmos chamar a função B () mais de uma vez, teremos uma situação de pânico semelhante.



Como não usar goroutines



Nesta seção, você aprenderá uma maneira ingênua de classificar números naturais usando goroutines. O programa que veremos é chamado sillySort.go. Vamos dividir em duas partes. A primeira parte de sillySort.go se parece com isto:



package main

import (
       "fmt"
       "os"
       "strconv"
       "sync"
       "time"
)

func main() {
      arguments := os.Args

      if len(arguments) == 1 {
          fmt.Println(os.Args[0], "n1, n2, [n]")
          return
      }

      var wg sync.WaitGroup
      for _, arg := range arguments[1:] {
           n, err := strconv.Atoi(arg)
           if err != nil || n < 0 {
                fmt.Print(". ")
                continue
           }


A segunda parte de sillySort.go contém o seguinte código Go:



           wg.Add(1)
           go func(n int) {
                defer wg.Done()
                time.Sleep(time.Duration(n) * time.Second)
                fmt.Print(n, " ")
           }(n)
      }

      wg.Wait()
      fmt.Println()
}


A classificação é realizada chamando a função time.Sleep () - quanto maior o número natural, mais tempo leva para que o operador fmt.Print () seja executado!



Executar o sillySort.go produzirá resultados como este:



$ go run sillySort.go a -1 1 2 3 5 0 100 20 60
. . 0 1 2 3 5 20 60 100
$ go run sillySort.go a -1 1 2 3 5 0 100 -1 a 20 hello 60
. . . . . 0 1 2 3 5 20 60 100
$ go run sillySort.go 0 0 10 2 30 3 4 30
0 0 2 3 4 10 30 30




Sobre o autor



Mihalis Tsoukalos é um administrador UNIX, programador, administrador de banco de dados e matemático. Gosta de escrever livros e artigos técnicos, aprender coisas novas. Além deste livro, Michalis escreveu Go Systems Programming, bem como mais de 250 artigos técnicos para muitas revistas, incluindo Sys Admin, MacTech, Linux User and Developer, Usenix; login:, Linux Format e Linux Journal. Os interesses de pesquisa de Michalis são bancos de dados, visualização, estatística e aprendizado de máquina.



Sobre o editor científico



Mat Ryer escreve programas de computador desde os seis anos de idade: primeiro no BASIC para o ZX Spectrum, e depois, com o pai, no AmigaBASIC e AMOS para o Commodore Amiga. Ele passou muito tempo copiando manualmente o código do log de formato do Amiga, alterando os valores das variáveis ​​ou referências de instrução GOTO para ver o que resultava disso. O mesmo espírito de exploração e obsessão com a programação levou Matt, de 18 anos, a trabalhar para uma organização local em Mansfield, no Reino Unido, onde começou a construir sites e outros serviços online.



Após vários anos trabalhando com diferentes tecnologias em diferentes campos, não apenas em Londres, mas ao redor do mundo, Mat voltou sua atenção para uma nova linguagem de programação de sistemas chamada Go, usada pela primeira vez no Google. Como Go estava resolvendo problemas técnicos muito importantes, Mat começou a usar a linguagem para solução de problemas quando Go ainda estava em beta e continuou a programar desde então. Mat trabalhou em vários projetos de código aberto, criou vários pacotes Go, incluindo Testify, Moq, Silk e Is, bem como o kit de ferramentas de desenvolvedor MacOS BitBar.



Desde 2018, Mat é cofundador da Machine Box, mas ainda participa de conferências, escreve sobre Go em seu blog e é um membro ativo da comunidade Go.



»Mais detalhes sobre o livro podem ser encontrados no site da editora

» Índice

» Trecho



Para Habitantes desconto de 25% no cupom - Golang



Mediante o pagamento da versão em papel do livro, é enviado um e-book para o e-mail.



All Articles