En savoir plus sur les goroutines
La simultanéité est la composition d’activités indépendantes, comme le travail qu’effectue un serveur Web lorsqu’il traite plusieurs demandes utilisateur simultanément, mais de manière autonome. La simultanéité est actuellement présente dans de nombreux programmes. Les serveurs Web en sont un exemple, mais vous pouvez également voir en la nécessité pour traiter de gros volumes de données par lots.
Go a deux styles pour l’écriture de programmes simultanés. L’un est un style traditionnel que vous avez pu utiliser dans d’autres langages avec les threads. Dans ce module, vous allez découvrir le style de Go, où les valeurs sont passées entre des activités indépendantes appelées goroutines pour communiquer des processus.
Si c’est la première fois que vous vous familiarisez avec l’accès concurrentiel, nous vous encourageons à consacrer un peu de temps à examiner et utiliser chaque extrait de code.
Approche de Go pour l’accès simultané
En règle générale, le plus grand problème dans l’écriture de programmes simultanés est le partage de données entre les processus. Go adopte une approche différente des autres langages de programmation avec la communication, car Go transmet les données entre les canaux. Cette approche signifie qu’une seule activité (goroutine) a accès aux données et qu’il n’y a pas de condition de simultanéité par conception. À mesure que vous découvrez les goroutines et les canaux de ce module, vous allez mieux comprendre l’approche de l’accès simultané de Go.
L’approche de Go peut être résumée par le slogan suivant : « Ne communiquez pas en partageant la mémoire ; au lieu de cela, partagez la mémoire en communiquant. » Nous aborderons cette approche dans les sections suivantes, mais vous pouvez également en savoir plus dans le billet du blog Go Partager la mémoire en communiquant.
Comme nous l’avons mentionné auparavant, Go inclut aussi des primitives d’accès concurrentiel de bas niveau. Cependant, nous allons aborder seulement l’approche idiomatique de Go pour l’accès concurrentiel dans ce module.
Commençons par explorer les goroutines.
Goroutines
Une goroutine est une activité simultanée dans un thread léger, et non dans un système d’exploitation. Supposons que vous disposez d’un programme qui écrit dans la sortie et d’une autre fonction qui calcule les choses comme l’ajout de deux nombres. Un programme simultané peut avoir plusieurs goroutines appelant les deux fonctions en même temps.
Nous pouvons affirmer que la première goroutine exécutée est la fonction main()
. Si vous voulez créer une autre goroutine, vous devez utiliser le mot clé go
avant d’appeler la fonction, comme dans cet exemple :
func main(){
login()
go launch()
}
Vous constaterez également que de nombreux programmes utilisent plutôt des fonctions anonymes pour créer des goroutines, comme dans ce code :
func main(){
login()
go func() {
launch()
}()
}
Pour voir ces goroutines en action, nous allons écrire un programme avec accès concurrentiel.
Écrire un programme simultané
Étant donné que nous souhaitons nous concentrer uniquement sur la partie simultanée, nous allons utiliser un programme existant qui vérifie si un point de terminaison d’API répond ou non. Voici le code :
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
for _, api := range apis {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
continue
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
Quand vous exécutez le code précédent, vous obtenez la sortie suivante :
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 1.658436834 seconds!
Rien n’est en dehors de la norme ici, mais nous pouvons faire mieux. Peut-être pouvons-nous vérifier tous les sites en même temps ? Au lieu de prendre presque deux secondes, le programme peut terminer la tâche en moins de 500 ms.
Notez que l’extrait de code à exécuter simultanément est celui qui effectue l’appel HTTP au site. En d’autres termes, nous devons créer une goroutine pour chaque API vérifiée par le programme.
Pour créer une goroutine, nous devons utiliser le mot clé go
avant d’appeler une fonction. Toutefois, nous n’avons pas de fonction ici. Refactorisons ce code et créons une nouvelle fonction, comme ceci :
func checkAPI(api string) {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
return
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
Notez que nous n’avons plus besoin du mot clé continue
, car nous ne sommes pas dans une boucle for
. Pour arrêter le flux d’exécution de la fonction, nous utilisons le mot clé return
. Maintenant, nous devons modifier le code de la fonction main()
pour créer une goroutine par API, comme suit :
for _, api := range apis {
go checkAPI(api)
}
Exécutez à nouveau le programme et regardez ce qu’il se passe.
Vous pouvez voir que le programme ne vérifie plus les API. Vous devriez voir une sortie semblable à la suivante :
Done! It took 1.506e-05 seconds!
C’était rapide ! Que s’est-il passé ? Vous voyez le message final indiquant que le programme s’est terminé, car Go a créé une goroutine pour chaque site dans la boucle et il est passé immédiatement à la ligne suivante.
Même s’il ne ressemble pas à l’exécution de la fonction checkAPI
, elle est en cours d’exécution. Il n’a pas eu le temps de terminer. Notez ce qui se passe si vous incluez une minuterie de veille juste après la boucle, comme suit :
for _, api := range apis {
go checkAPI(api)
}
time.Sleep(3 * time.Second)
Désormais, lorsque vous réexécutez le programme, vous pouvez voir une sortie semblable à celle-ci :
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 3.002114573 seconds!
Il semble que cela fonctionne, n’est-ce pas ? Eh bien, pas précisément. Que se passe-t-il si vous souhaitez ajouter un nouveau site à la liste ? Il se peut que trois secondes ne suffisent pas. Comment l’évalueriez-vous ? Vous ne le pouvez pas. Il doit y avoir une meilleure méthode, et c’est ce que nous aborderons dans la section suivante quand nous parlerons des canaux.