Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🐹
Intermediaire 25 min Go

Goroutines et channels en Go

Pourquoi Goroutines et channels en Go ?

Contexte réel : pourquoi un dev a besoin de ca au quotidien

Le développement d'applications modernes nécessite souvent la gestion concrète des tâches parallèles et asynchrones pour assurer une performance élevée et une réponse rapide à l'utilisateur. En utilisant les goroutines et les channels en Go, les développeurs peuvent structurer leur code de manière plus efficace et éviter les problèmes liés aux threadings traditionnels.

Un cas d'usage concret en 2-3 phrases

Imaginez un service web qui doit traiter des images uploadées par les utilisateurs. Chaque image peut être redimensionnée, compressée ou convertie en format différent de manière indépendante. En utilisant des goroutines pour chaque tâche et des channels pour communiquer entre elles, le serveur peut gérer plusieurs images simultanément sans surcharger la CPU.

Prerequis

  • Connaissances en Go
    • Variables, structures de contrôle (if, for), fonctions
  • Familiarité avec les concepts de concurrence
    • Threadings et synchronisation
  • Installation d'Go : https://golang.org/dl/

Concepts fondamentaux

Goroutines

Une goroutine est une tâche légère qui peut être lancée en parallèle. Elle est similaire à un thread mais avec une gestion plus efficace de la mémoire et des ressources.

Schéma mental :

|-----------------|
| Goroutine 1     |
|                 |
|-----------------|
        |
        v
|-----------------|
| Goroutine 2     |
|                 |
|-----------------|

Code fonctionnel :

package main

import (
	"fmt"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

Channels

Un channel est un moyen de passer des valeurs entre les goroutines. Il permet d'échanger et de synchroniser les données entre plusieurs routines en temps réel.

Schéma mental :

|-----------------|
| Goroutine 1     |
|                 |
|-----------------|
        |
        v
|-----------------|
| Channel         |
|                 |
|-----------------|
        |
        v
|-----------------|
| Goroutine 2     |
|                 |
|-----------------|

Code fonctionnel :

package main

import (
	"fmt"
)

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // envoie la somme au channel
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // récupère les sommes des channels

	fmt.Println(x, y, x+y)
}

Mise en pratique : projet fil rouge

Projet : Mini-gestionnaire de tâches

Le mini-gestionnaire de tâches permettra aux utilisateurs d'ajouter, supprimer et afficher leurs tâches. Les ajouts seront traités par une goroutine pour éviter le blocage de la fonction principale.

Étapes

  1. Création du fichier main.go

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type Task struct {
        id    int
        title string
        done  bool
    }
    
    var tasks = []Task{}
    var mu sync.Mutex
    
    func addTask(title string) {
        mu.Lock()
        task := Task{id: len(tasks) + 1, title: title, done: false}
        tasks = append(tasks, task)
        mu.Unlock()
    }
    
    func removeTask(id int) {
        mu.Lock()
        for i, t := range tasks {
            if t.id == id {
                tasks = append(tasks[:i], tasks[i+1:]...)
                break
            }
        }
        mu.Unlock()
    }
    
    func listTasks() []Task {
        return tasks
    }
    
    func main() {
        var wg sync.WaitGroup
    
        go addTask("Faire les courses")
        go addTask("Nettoyer la maison")
    
        wg.Add(1)
        go func() {
            defer wg.Done()
            time.Sleep(time.Second * 2)
            removeTask(1)
        }()
    
        wg.Wait()
    
        for _, task := range listTasks() {
            fmt.Printf("ID: %d, Title: %s, Done: %t\n", task.id, task.title, task.done)
        }
    }
    
  2. Commande à exécuter

    go run main.go
    

Explications

  • Task : Structure représentant une tâche.
  • tasks : Slice de tâches partagées entre les goroutines.
  • mu : Mutex pour synchroniser l'accès aux données partagées.
  • addTask : Ajoute une nouvelle tâche à la liste en utilisant une goroutine.
  • removeTask : Supprime une tâche de la liste en utilisant une goroutine.
  • listTasks : Renvoie la liste des tâches.

Erreurs frequentes et debugging

1. Erreur : Goroutine sans channel

Code incorrect :

package main

func main() {
	go func() {
		fmt.Println("Hello from goroutine")
	}()
}

Code correct :

package main

import "fmt"

func main() {
	ch := make(chan string)
	go func() {
		ch <- "Hello from goroutine"
	}()

	msg := <-ch
	fmt.Println(msg)
}

2. Erreur : Channel non bufferisé

Code incorrect :

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		time.Sleep(time.Second)
		fmt.Println("Worker", id, "started job", j)
		results <- j * 2
	}
}

func main() {
	jobs := make(chan int)
	results := make(chan int)

	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	for j := 1; j <= 5; j++ {
		jobs <- j
	}
	close(jobs)

	for a := 1; a <= 5; a++ {
		<-results
	}
}

Code correct :

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		time.Sleep(time.Second)
		fmt.Println("Worker", id, "started job", j)
		results <- j * 2
	}
}

func main() {
	jobs := make(chan int, 100)
	results := make(chan int, 100)

	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	for j := 1; j <= 5; j++ {
		jobs <- j
	}
	close(jobs)

	for a := 1; a <= 5; a++ {
		<-results
	}
}

3. Erreur : Deadlock

Code incorrect :

package main

import (
    "fmt"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

Code correct :

package main

import (
    "fmt"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

Pour aller plus loin

1. Buffering channels

L'apprentissage des channels bufferisés peut aider à gérer les cas où le producteur ne produit pas aussi rapidement que le consommateur.

Liens :

2. Select statements

Les select statements offrent une façon plus flexible de travailler avec plusieurs channels en attendant les valeurs à recevoir.

Liens :

3. Unbuffered channels vs Buffered channels

Il est important d'understandre la différence entre les channels bufferisés et non bufferisés et quand utiliser chaque type.

Liens :

Défi pratique

Défi : Mini-API de blog

Créez une API simple en utilisant les goroutines et les channels pour gérer des posts. Les fonctionnalités devraient inclure l'ajout, la suppression et la récupération de posts.

Instructions :

  1. Créez un fichier main.go
  2. Implémentez les endpoints suivants :
    • POST /posts pour ajouter un post
    • GET /posts pour récupérer tous les posts
    • DELETE /posts/:id pour supprimer un post
  3. Utilisez des goroutines pour gérer les requêtes en parallèle.
  4. Utilisez des channels pour synchroniser l'accès aux données partagées.

Indice :

  • Pour simplifier, utilisez un slice pour stocker les posts plutôt qu'une base de données réelle.
  • Assurez-vous que chaque endpoint retourne le bon code HTTP et les bons headers.

Ressources supplémentaires :

Besoin d'aide sur Go ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Qu'est-ce qu'une goroutine en Go?
Une goroutine est une tâche légère qui peut être lancée et exécutée concurremment avec d'autres goroutines dans un même programme Go.
Comment créer une goroutine en Go?
Pour créer une goroutine, vous utilisez le mot-clé `go` suivi de la fonction que vous voulez exécuter en parallèle. Par exemple : `go maFonction()`.
Qu'est-ce qu'un channel en Go et comment est-il utilisé?
Un channel est une communication synchrone entre des goroutines. Il permet de passer des données d'une goroutine à l'autre en sécurisant le partage des ressources.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.