Golang Pro: redes, subprocesos múltiples, estructuras de datos y aprendizaje automático con Go

imagen ¡Hola habitantes!



¿Ya está familiarizado con los conceptos básicos del lenguaje Go? Entonces este libro es para ti. Michalis Tsukalos demostrará las capacidades del lenguaje, dará explicaciones claras y sencillas, dará ejemplos y sugerirá patrones de programación efectivos. A medida que aprenda los matices de Go, dominará los tipos de datos y las estructuras del lenguaje, así como el empaquetado, la concurrencia, la programación de redes, el diseño del compilador, la optimización y más. Los materiales y ejercicios al final de cada capítulo ayudarán a reforzar sus nuevos conocimientos. Un material único será el capítulo sobre aprendizaje automático en Go, que lo guiará desde las técnicas estadísticas básicas hasta la regresión y la agrupación en clústeres. Aprenderá técnicas de clasificación, redes neuronales y detección de anomalías. En las secciones aplicadas, aprenderá a usar Go con Docker y Kubernetes, Git, WebAssembly, JSON y más.



De que es este libro
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-, . : -, , , -, .



Y de nuevo sobre los canales Go



Una vez que se usa la palabra clave select, hay varias formas únicas de usar los canales Go que hacen mucho más de lo que vio en el Capítulo 9. En esta sección, aprenderá sobre los diferentes usos de los canales Go.



Permítame recordarle que el valor cero para los canales es nulo, y si envía un mensaje a un canal cerrado, el programa entrará en modo de pánico. Pero si intenta leer datos de un canal cerrado, obtendrá un valor cero para este tipo de canal. Por lo tanto, después de cerrar el canal, ya no puede escribir datos en él, pero aún puede leerlo.



Para que un canal se cierre, no es necesario que esté diseñado para recibir únicamente datos. Además, el canal cero siempre está bloqueado, es decir, un intento de leer o escribir desde el canal cero bloqueará el canal. Esta propiedad de los canales es muy útil cuando necesita deshabilitar una rama de una instrucción select estableciendo la variable de canal en nil.



Finalmente, al intentar cerrar el canal cero, el programa entrará en pánico. Veamos el ejemplo de closeNilChannel.go:



package main

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


La ejecución de closeNilChannel.go producirá el siguiente resultado:



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


Canales de señal



Un canal de señalización es un canal que se usa solo para señalización. En pocas palabras, el canal de señalización se puede utilizar cuando desee informar a otro programa sobre algo. No es necesario utilizar canales de señalización para transferir datos.



Los canales de señalización no deben confundirse con el manejo de señales UNIX discutido en el Capítulo 8, son cosas completamente diferentes.


Más adelante en este capítulo se analiza un ejemplo de código que utiliza canales de señalización.



Canales en búfer



El tema de esta subsección son las tuberías en búfer. Estos son canales que permiten al programador de Go poner rápidamente trabajos en cola para manejar más solicitudes. Además, se pueden utilizar como semáforos para limitar el ancho de banda de una aplicación.



El método que aquí se presenta funciona así: todas las solicitudes entrantes se redirigen a un canal, que las procesa a su vez. Cuando el canal termina de procesar la solicitud, envía un mensaje a la persona que llama originalmente que el canal está listo para procesar una nueva solicitud. Por lo tanto, la capacidad de búfer de un canal limita el número de solicitudes simultáneas que el canal puede almacenar.



Veremos este método usando el código del programa bufChannel.go como ejemplo. Dividámoslo en cuatro partes.



La primera parte del código bufChannel.go se ve así:



package main

import (
       "fmt"
)


La segunda parte del archivo bufChannel.go contiene el siguiente código Go:



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


La definición de números presentada aquí le permite almacenar hasta cinco enteros en esta tubería.



La tercera parte de bufChannel.go contiene el siguiente código Go:



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


En este código, intentamos poner diez números en el canal de números. Sin embargo, dado que los números solo tienen espacio para cinco enteros, no podemos almacenar los diez enteros en ellos.



El resto del código Go de bufChannel.go tiene este aspecto:



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


En este código de Go, intentamos leer el contenido del canal de números usando un bucle for y una declaración de selección. Siempre que haya algo para leer en el canal de números, se ejecutará la primera rama de la instrucción de selección. Cuando el canal de números está vacío, se ejecuta la rama predeterminada.



La ejecución de bufChannel.go producirá el siguiente 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!


Canales cero



En esta sección, aprenderá sobre el canal cero. Este es un tipo especial de canal que siempre está bloqueado. Veremos estos canales usando el programa nilChannel.go como ejemplo. Dividámoslo en cuatro partes de código.



La primera parte de nilChannel.go se ve así:



package main

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


La segunda parte de nilChannel.go contiene el siguiente 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)
           }
      }
}


Aquí, usando la función add () como ejemplo, se muestra cómo se usa el canal cero. El operador <-tC bloquea el canal C del temporizador t durante el tiempo especificado en la llamada a time.NewTimer (). No confunda el canal c, que es un argumento de función, con el canal tC, que pertenece al temporizador t. Cuando expira el tiempo, el temporizador envía un valor al canal tC, que inicia la ejecución de la rama correspondiente de la instrucción de selección: establece el canal c en cero y muestra el valor de la variable suma.



El tercer fragmento de código nilChannel.go tiene este aspecto:



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


El propósito de la función send () es generar números aleatorios y enviarlos al canal siempre que el canal esté abierto.



El resto del código de Go en nilChannel.go se ve así:



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


La función time.Sleep () es necesaria para que las dos gorutinas tengan tiempo suficiente para ejecutarse.



Ejecutar nilChannel.go producirá los siguientes resultados:



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


Dado que el número de veces que se ejecuta la primera rama de la instrucción select en add () no es fijo, ejecutar nilChannel.go varias veces producirá resultados diferentes.



Canales de canal



Un canal de canal es un tipo especial de variable de canal que funciona con otros canales en lugar de los tipos de variables habituales. Sin embargo, aún debe declarar un tipo de datos para un canal de canales. Para definir el canal de canales, use la palabra clave chan dos veces seguidas, como se muestra en la siguiente declaración:



c1 := make(chan chan int)


Otros tipos de canales presentados en este capítulo son más populares y útiles que los canales de canal.


Analizaremos el uso de canales de canal utilizando el código de ejemplo que se encuentra en el archivo chSquare.go. Dividámoslo en cuatro partes.



La primera parte de chSquare.go se ve así:



package main

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

var times int


La segunda parte de chSquare.go contiene el siguiente 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
      }
}


Habiendo declarado un canal regular de tipo int, lo pasamos a la variable channel channel. Luego, usando la instrucción select, tenemos la oportunidad de leer datos de un canal int regular o salir de la función usando el canal de señal f.



Después de leer un valor del canal c, ejecutamos un ciclo for que calcula la suma de todos los enteros desde 0 hasta el valor entero que acabamos de leer. Luego enviamos el valor calculado al canal int c, y eso es todo.



La tercera parte de chSquare.go contiene el siguiente 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)


En la última línea de este fragmento de código, declaramos una variable de canal llamada cc. Esta variable es la estrella de este programa, porque todo depende de ella. La variable cc se pasa a f1 () y se usa en el siguiente ciclo for.



El resto del código de chSquare.go Go se ve así:



   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)
    }
}


El canal f es el canal de señal para el final de la gorutina cuando se realiza todo el trabajo. La instrucción ch: = <-cc le permite obtener un canal regular de una variable de canal para pasar un valor int allí usando el operador ch <- i. Después de eso, leemos los datos de la tubería usando un bucle for. La función f1 () está programada para devolver un valor, pero también podemos leer varios valores. Tenga en cuenta que cada valor de i es servido por su propia goroutine.



El tipo de canal de señal puede ser cualquier cosa, incluido el bool utilizado en el código anterior y la estructura {}, que se utilizará para el canal de señal en la siguiente sección. La principal ventaja de un canal de señalización de tipo struct {} es que no se pueden enviar datos a dicho canal, esto evita que se produzcan errores.



La ejecución de chSquare.go producirá 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


Elegir el orden de ejecución



No es necesario que haga suposiciones sobre la secuencia de ejecución de las goroutines. Sin embargo, hay ocasiones en las que es necesario controlar este orden. En esta subsección, aprenderá a hacer esto usando canales de señalización.



Puede preguntar, "¿Por qué crear goroutines y luego ejecutarlas en un orden dado cuando es mucho más fácil hacer lo mismo con funciones regulares?" La respuesta es simple: goroutines se pueden ejecutar simultáneamente y esperar a que se completen otras goroutines, mientras que las funciones no pueden porque se ejecutan secuencialmente.


En esta subsección, veremos un programa Go llamado defineOrder.go. Dividámoslo en cinco partes. La primera parte de defineOrder.go se ve así:



package main

import (
       "fmt"
       "time"
)

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


La función A () está bloqueada por el canal almacenado en el parámetro a. Tan pronto como este canal se desbloquee en main (), la función A () comenzará a funcionar. Finalmente, cerrará el canal b, desbloqueando así otra función, en este caso, B ().



La segunda parte de defineOrder.go contiene el siguiente código Go:



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


La lógica de la función B () es la misma que la de A (). Esta función se bloquea hasta que se cierra el canal a. Luego hace su trabajo y cierra el canal b. Tenga en cuenta que los canales ayb se refieren a los nombres de los parámetros de función.



La tercera pieza de código para defineOrder.go se ve así:



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


La función C () está bloqueada y está esperando que el canal a se cierre antes de comenzar.



La cuarta parte de defineOrder.go contiene el siguiente código:



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


Estos tres canales se convertirán en parámetros para tres funciones.



El último fragmento de defineOrder.go contiene el siguiente 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)
}


Aquí, el programa realiza todas las funciones necesarias y luego cierra el canal x y duerme durante tres segundos.



La ejecución de defineOrder.go producirá el resultado deseado, aunque la función C () se llamará varias veces:



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


Llamar a C () varias veces como goroutine no causará problemas, porque C () no cierra ningún canal. Pero si llama a A () o B () más de una vez, lo más probable es que se muestre un mensaje de error, por ejemplo:



$ 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 puede ver, aquí se llamó dos veces a la función A (). Sin embargo, cuando A () cierra un canal, una de sus gorutinas detecta que el canal ya está cerrado y crea una situación de pánico cuando intenta cerrar ese canal nuevamente. Si intentamos llamar a la función B () más de una vez, obtenemos una situación de pánico similar.



Cómo no usar gorutinas



En esta sección, aprenderá una forma ingenua de ordenar números naturales usando goroutines. El programa que veremos se llama sillySort.go. Dividámoslo en dos partes. La primera parte de sillySort.go se ve así:



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
           }


La segunda parte de sillySort.go contiene el siguiente 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()
}


La clasificación se realiza llamando a la función time.Sleep (): cuanto mayor sea el número natural, más tiempo tardará en ejecutarse el operador fmt.Print ().



La ejecución de sillySort.go producirá 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 el Autor



Mihalis Tsoukalos es administrador, programador, administrador de bases de datos y matemático de UNIX. Le gusta escribir libros y artículos técnicos, aprender algo nuevo. Además de este libro, Michalis ha escrito Go Systems Programming, así como más de 250 artículos técnicos para muchas revistas, incluidas Sys Admin, MacTech, Linux User and Developer, Usenix; login:, Linux Format y Linux Journal. Los intereses de investigación de Michalis son las bases de datos, la visualización, las estadísticas y el aprendizaje automático.



Sobre el editor científico



Mat Ryer ha estado escribiendo programas de computadora desde los seis años: primero en BASIC para el ZX Spectrum y luego, con su padre, en AmigaBASIC y AMOS para el Commodore Amiga. Pasó mucho tiempo copiando manualmente el código del registro del formato Amiga, cambiando los valores de las variables o las referencias de declaraciones GOTO para ver qué salía de ellas. El mismo espíritu de exploración y obsesión por la programación llevó a Matt, de 18 años, a trabajar para una organización local en Mansfield, Reino Unido, donde comenzó a crear sitios web y otros servicios en línea.



Después de varios años de trabajar con diversas tecnologías en diversos campos, no solo en Londres sino en todo el mundo, Mat centró su atención en un nuevo lenguaje de programación de sistemas llamado Go, utilizado por primera vez en Google. Debido a que Go estaba resolviendo problemas técnicos muy candentes y candentes, Mat comenzó a usar el lenguaje para resolver problemas cuando Go todavía estaba en beta y ha continuado programando en él desde entonces. Mat ha trabajado en varios proyectos de código abierto, ha creado varios paquetes Go, incluidos Testify, Moq, Silk e Is, y el kit de herramientas para desarrolladores de MacOS BitBar.



Desde 2018, Mat ha sido cofundador de Machine Box, pero todavía participa en conferencias, escribe sobre Go en su blog y es un miembro activo de la comunidad Go.



»Más detalles sobre el libro se pueden encontrar en el sitio web de la editorial

» Tabla de contenido

» Extracto



para los habitantes un 25% de descuento en el cupón - Golang



Tras el pago de la versión impresa del libro, se envía un libro electrónico al correo electrónico.



All Articles