Golang Pro: mise en réseau, multithreading, structures de données et apprentissage automatique avec Go

image Bonjour les Habitants!



Connaissez-vous déjà les bases du langage Go? Alors, ce livre est pour toi. Michalis Tsukalos démontrera les capacités du langage, donnera des explications claires et simples, donnera des exemples et suggérera des modèles de programmation efficaces. En explorant les nuances de Go, vous maîtriserez les types et structures de données du langage, ainsi que l'empaquetage, la simultanéité, la programmation réseau, la conception du compilateur, l'optimisation, etc. Le matériel et les exercices à la fin de chaque chapitre vous aideront à renforcer vos nouvelles connaissances. Un matériel unique sera le chapitre sur l'apprentissage automatique dans Go, qui vous guidera des techniques statistiques de base à la régression et au clustering. Vous apprendrez la classification, les réseaux de neurones et les techniques de détection des anomalies. Dans les sections appliquées, vous apprendrez à utiliser Go avec Docker et Kubernetes, Git, WebAssembly, JSON, etc.



De quoi parle ce livre
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-, . : -, , , -, .



Et encore une fois sur les canaux Go



Une fois que le mot-clé select est utilisé, il existe plusieurs façons uniques d'utiliser les tuyaux Go qui font bien plus que ce que vous avez vu au chapitre 9. Dans cette section, vous découvrirez les différentes façons d'utiliser les tuyaux Go.



Permettez-moi de vous rappeler que la valeur zéro pour les chaînes est nulle, et si vous envoyez un message à une chaîne fermée, le programme passera en mode panique. Mais si vous essayez de lire des données à partir d'un canal fermé, vous obtiendrez une valeur nulle pour ce type de canal. Ainsi, après avoir fermé le canal, vous ne pouvez plus y écrire de données, mais vous pouvez toujours le lire.



Pour qu'un canal soit fermé, il n'est pas nécessaire qu'il soit conçu pour recevoir uniquement des données. De plus, le canal zéro est toujours bloqué, c'est-à-dire qu'une tentative de lecture ou d'écriture à partir du canal zéro bloquera le canal. Cette propriété de channels est très utile lorsque vous devez désactiver une branche d'une instruction select en définissant la variable channel sur nil.



Enfin, en essayant de fermer le canal zéro, le programme soulèvera une panique. Regardons l'exemple closeNilChannel.go:



package main

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


L'exécution de closeNilChannel.go produira le résultat suivant:



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


Canaux de signal



Un canal de signalisation est un canal qui n'est utilisé que pour la signalisation. En termes simples, le canal de signalisation peut être utilisé lorsque vous souhaitez informer un autre programme de quelque chose. Les canaux de signalisation n'ont pas besoin d'être utilisés pour transférer des données.



Les canaux de signalisation ne doivent pas être confondus avec la gestion des signaux UNIX décrite au chapitre 8, ce sont des choses complètement différentes.


Un exemple de code utilisant des canaux de signalisation est présenté plus loin dans ce chapitre.



Canaux tamponnés



Le sujet de cette sous-section est les tuyaux tamponnés. Ce sont des canaux qui permettent au planificateur Go de mettre rapidement en file d'attente les travaux pour traiter davantage de demandes. De plus, ils peuvent être utilisés comme sémaphores pour limiter la bande passante d'une application.



La méthode présentée ici fonctionne comme ceci: toutes les demandes entrantes sont redirigées vers un canal, qui les traite à son tour. Lorsque le canal a fini de traiter la demande, il envoie un message à l'appelant d'origine indiquant que le canal est prêt à traiter une nouvelle demande. Ainsi, la capacité de tampon d'un canal limite le nombre de requêtes simultanées que le canal peut stocker.



Nous examinerons cette méthode en utilisant le code du programme bufChannel.go comme exemple. Divisons-le en quatre parties.



La première partie du code bufChannel.go ressemble à ceci:



package main

import (
       "fmt"
)


Le deuxième morceau du fichier bufChannel.go contient le code Go suivant:



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


La définition des nombres présentée ici vous permet de stocker jusqu'à cinq entiers dans ce tube.



La troisième partie de bufChannel.go contient le code Go suivant:



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


Dans ce code, nous avons essayé de mettre dix numéros dans le canal des numéros. Cependant, étant donné que les nombres n'ont de place que pour cinq entiers, nous ne pouvons pas y stocker les dix entiers.



Le reste du code Go de bufChannel.go ressemble à ceci:



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


Dans ce code Go, nous avons essayé de lire le contenu du canal de nombres en utilisant une boucle for et une instruction select. Tant qu'il y a quelque chose à lire dans le canal des nombres, la première branche de l'instruction select s'exécutera. Lorsque le canal de numéros est vide, la branche par défaut est exécutée.



L'exécution de bufChannel.go produira le résultat suivant:



$ 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!


Zéro canaux



Dans cette section, vous découvrirez le canal zéro. Il s'agit d'un type spécial de chaîne qui est toujours bloqué. Nous examinerons ces canaux en utilisant le programme nilChannel.go comme exemple. Divisons-le en quatre morceaux de code.



La première partie de nilChannel.go ressemble à ceci:



package main

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


La deuxième partie de nilChannel.go contient le code Go suivant:



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


Ici, en utilisant la fonction add () comme exemple, montre comment le canal zéro est utilisé. L'opérateur <-tC bloque le canal C du temporisateur t pendant le temps spécifié dans l'appel à time.NewTimer (). Ne confondez pas le canal c, qui est un argument de fonction, avec le canal tC, qui appartient au temporisateur t. Lorsque le temps expire, le temporisateur envoie une valeur au canal tC, qui lance l'exécution de la branche correspondante de l'instruction select - il met le canal c à zéro et affiche la valeur de la variable somme.



Le troisième extrait de code nilChannel.go ressemble à ceci:



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


Le but de la fonction send () est de générer des nombres aléatoires et de les envoyer au canal tant que le canal est ouvert.



Le reste du code Go dans nilChannel.go ressemble à ceci:



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


La fonction time.Sleep () est nécessaire pour que les deux goroutines aient suffisamment de temps pour s'exécuter.



L'exécution de nilChannel.go produira les résultats suivants:



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


Étant donné que le nombre de fois où la première branche de l'instruction select dans add () est exécutée n'est pas fixe, exécuter nilChannel.go plusieurs fois produira des résultats différents.



Chaînes de canal



Un canal de canal est un type spécial de variable de canal qui fonctionne avec d'autres canaux au lieu des types de variables habituels. Cependant, vous devez toujours déclarer un type de données pour un canal de canaux. Pour définir le canal des canaux, utilisez le mot-clé chan deux fois de suite, comme indiqué dans l'instruction suivante:



c1 := make(chan chan int)


Les autres types de canaux présentés dans ce chapitre sont plus populaires et utiles que les canaux de canaux.


Nous allons parcourir l'utilisation des canaux de canaux en utilisant l'exemple de code trouvé dans le fichier chSquare.go. Divisons-le en quatre parties.



La première partie de chSquare.go ressemble à ceci:



package main

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

var times int


La deuxième partie de chSquare.go contient le code Go suivant:



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


Après avoir déclaré un canal régulier de type int, nous le passons à la variable channel channel. Ensuite, en utilisant l'instruction select, nous avons la possibilité de lire des données à partir d'un canal int régulier ou de quitter la fonction en utilisant le canal de signal f.



Après avoir lu une valeur du canal c, nous exécutons une boucle for qui calcule la somme de tous les entiers de 0 à la valeur entière que nous venons de lire. Ensuite, nous envoyons la valeur calculée au canal int c, et c'est tout.



La troisième partie de chSquare.go contient le code Go suivant:



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)


Dans la dernière ligne de cet extrait de code, nous déclarons une variable de canal nommée cc. Cette variable est la star de ce programme, car tout en dépend. La variable cc est passée à f1 () et utilisée dans la prochaine boucle for.



Le reste du code chSquare.go Go ressemble à ceci:



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


Le canal f est le canal de signal pour la fin du goroutine lorsque tout le travail est terminé. L'instruction ch: = <-cc vous permet d'obtenir un canal normal à partir d'une variable de canal pour y passer une valeur int en utilisant l'opérateur ch <- i. Après cela, nous lisons les données du tube en utilisant une boucle for. La fonction f1 () est programmée pour renvoyer une valeur, mais nous pouvons également lire plusieurs valeurs. Notez que chaque valeur i est servie par sa propre goroutine.



Le type de canal de signal peut être n'importe quoi, y compris le booléen utilisé dans le code précédent et le struct {}, qui sera utilisé pour le canal de signal dans la section suivante. Le principal avantage d'un canal de signalisation de type struct {} est que les données ne peuvent pas être envoyées vers un tel canal, ce qui empêche les erreurs de se produire.



L'exécution de chSquare.go produira des résultats comme celui-ci:



$ 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


Choisir l'ordre d'exécution



Vous n'êtes pas obligé de faire des hypothèses sur la séquence d'exécution des goroutines. Cependant, il y a des moments où il est nécessaire de contrôler cet ordre. Dans cette sous-section, vous apprendrez comment procéder en utilisant les canaux de signalisation.



Vous pouvez vous demander: "Pourquoi créer des goroutines et les exécuter dans un ordre donné alors qu'il est beaucoup plus facile de faire de même avec des fonctions régulières?" La réponse est simple: les goroutines peuvent s'exécuter simultanément et attendre que les autres goroutines se terminent, alors que les fonctions ne le peuvent pas car elles s'exécutent séquentiellement.


Dans cette sous-section, nous examinerons un programme Go appelé defineOrder.go. Divisons-le en cinq parties. La première partie de defineOrder.go ressemble à ceci:



package main

import (
       "fmt"
       "time"
)

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


La fonction A () est bloquée par le canal mémorisé dans le paramètre a. Dès que ce canal est déverrouillé dans main (), la fonction A () commencera à fonctionner. Enfin, il fermera le canal b, débloquant ainsi une autre fonction - dans ce cas, B ().



La deuxième partie de defineOrder.go contient le code Go suivant:



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


La logique de la fonction B () est la même que celle de A (). Cette fonction est bloquée jusqu'à la fermeture du canal a. Ensuite, il fait son travail et ferme le canal b. Notez que les canaux a et b font référence aux noms des paramètres de fonction.



Le troisième morceau de code pour defineOrder.go ressemble à ceci:



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


La fonction C () est bloquée et attend la fermeture du canal a avant de démarrer.



La quatrième partie de defineOrder.go contient le code suivant:



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


Ces trois canaux deviendront des paramètres pour trois fonctions.



Le dernier extrait de defineOrder.go contient le code Go suivant:



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

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


Ici, le programme exécute toutes les fonctions nécessaires, puis ferme le canal x et dort pendant trois secondes.



L'exécution de defineOrder.go produira le résultat souhaité, même si la fonction C () sera appelée plusieurs fois:



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


Appeler C () plusieurs fois en tant que goroutine ne posera aucun problème, car C () ne ferme aucun canal. Mais si vous appelez A () ou B () plus d'une fois, il est fort probable qu'un message d'erreur s'affiche, par exemple:



$ 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


Comme vous pouvez le voir, ici la fonction A () a été appelée deux fois. Cependant, quand A () ferme un canal, l'un de ses goroutines détecte que le canal est déjà fermé et crée une situation de panique lorsqu'il essaie de refermer ce canal. Si nous essayons d'appeler la fonction B () plus d'une fois, nous obtenons une situation de panique similaire.



Comment ne pas utiliser les goroutines



Dans cette section, vous apprendrez une manière naïve de trier les nombres naturels à l'aide de goroutines. Le programme que nous allons examiner s'appelle sillySort.go. Divisons-le en deux parties. La première partie de sillySort.go ressemble à ceci:



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 deuxième partie de sillySort.go contient le code Go suivant:



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


Le tri est effectué en appelant la fonction time.Sleep () - plus le nombre naturel est grand, plus il faut de temps avant que l'opérateur fmt.Print () ne soit exécuté!



L'exécution de sillySort.go produira des résultats comme celui-ci:



$ 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




A propos de l'auteur



Mihalis Tsoukalos est un administrateur UNIX, un programmeur, un administrateur de base de données et un mathématicien. Aime écrire des livres et des articles techniques, apprendre quelque chose de nouveau. En plus de ce livre, Michalis a rédigé Go Systems Programming ainsi que plus de 250 articles techniques pour de nombreux magazines, notamment Sys Admin, MacTech, Linux User and Developer, Usenix; login:, Linux Format et Linux Journal. Les intérêts de recherche de Michalis sont les bases de données, la visualisation, les statistiques et l'apprentissage automatique.



À propos de l'éditeur scientifique



Mat Ryer écrit des programmes informatiques depuis l'âge de six ans: d'abord en BASIC pour le ZX Spectrum, puis, avec son père, sur AmigaBASIC et AMOS pour le Commodore Amiga. Il a passé beaucoup de temps à copier manuellement le code du magazine Amiga Format, en changeant les valeurs des variables ou des références aux instructions GOTO pour voir ce qui en résultait. Le même esprit d'exploration et d'obsession pour la programmation a conduit Matt, 18 ans, à travailler pour une organisation locale à Mansfield, au Royaume-Uni, où il a commencé à créer des sites Web et d'autres services en ligne.



Après plusieurs années de travail avec différentes technologies dans différents domaines, non seulement à Londres, mais dans le monde entier, Mat s'est tourné vers un nouveau langage de programmation de systèmes appelé Go, utilisé pour la première fois chez Google. Parce que Go résolvait des problèmes techniques très chauds et brûlants, Mat a commencé à utiliser le langage pour la résolution de problèmes alors que Go était encore en version bêta et a continué à y programmer depuis. Mat a travaillé sur divers projets open source, créé plusieurs packages Go, notamment Testify, Moq, Silk and Is et la boîte à outils de développement MacOS BitBar.



Depuis 2018, Mat est co-fondateur de Machine Box, mais il participe toujours à des conférences, écrit sur Go sur son blog et est un membre actif de la communauté Go.



»Plus de détails sur le livre peuvent être trouvés sur le site de l'éditeur

» Table des matières

» Extrait



Pour Habitants une réduction de 25% sur le coupon - Golang



Lors du paiement de la version papier du livre, un e-book est envoyé à l'e-mail.



All Articles