Implémenter des modèles de concurrence et de synchronisation en Go
En Go, il existe plusieurs modèles de programmation concurrente et outils de synchronisation qui peuvent vous aider à écrire des programmes concurrentiels efficaces et fiables. Explorons certains des concepts clés et comment les implémenter.
Modèles de concurrence
Pools de travailleurs (Worker Pools)
Un modèle de concurrence courant en Go est le pool de travailleurs. Dans ce modèle, vous créez un pool de goroutines travailleuses qui peuvent traiter des tâches de manière concurrente. Cela peut être utile pour les tâches qui peuvent être parallélisées, comme le traitement d'un grand ensemble de données ou l'exécution de requêtes réseau indépendantes.
Voici un exemple d'implémentation simple d'un pool de travailleurs en Go :
package main
import (
"fmt"
"sync"
)
func main() {
const numWorkers = 4
const numJobs = 10
var wg sync.WaitGroup
jobs := make(chan int, numJobs)
// Start worker goroutines
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", i, job)
}
}()
}
// Send jobs to the worker pool
for i := 0; i < numJobs; i++ {
jobs <- i
}
close(jobs)
wg.Wait()
}
Dans cet exemple, nous créons un canal pour stocker les tâches et un pool de 4 goroutines travailleuses qui extraient les tâches du canal et les traitent. Le sync.WaitGroup
est utilisé pour s'assurer que tous les travailleurs ont terminé avant que le programme ne se termine.
Pipelines
Un autre modèle de concurrence courant en Go est le modèle de pipeline. Dans ce modèle, vous créez une série d'étapes, où chaque étape traite des données et les transmet à l'étape suivante. Cela peut être utile pour traiter des données en une séquence d'étapes, comme la récupération de données, leur transformation et leur stockage.
Voici un exemple d'un simple pipeline en Go :
package main
import "fmt"
func main() {
// Create the pipeline stages
numbers := generateNumbers(10)
squares := squareNumbers(numbers)
results := printResults(squares)
// Run the pipeline
for result := range results {
fmt.Println(result)
}
}
func generateNumbers(n int) <-chan int {
out := make(chan int)
go func() {
for i := 0; i < n; i++ {
out <- i
}
close(out)
}()
return out
}
func squareNumbers(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for num := range in {
out <- num * num
}
close(out)
}()
return out
}
func printResults(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for num := range in {
out <- num
}
close(out)
}()
return out
}
Dans cet exemple, nous créons trois étapes de pipeline : generateNumbers
, squareNumbers
et printResults
. Chaque étape est une fonction qui lit depuis un canal d'entrée, traite les données et écrit les résultats dans un canal de sortie.
Outils de synchronisation
Go propose plusieurs primitives de synchronisation qui peuvent vous aider à coordonner l'accès concurrent à des ressources partagées et à éviter les conditions de concurrence.
Mutex
Le type sync.Mutex
est un verrou d'exclusion mutuelle qui vous permet de protéger les ressources partagées contre l'accès concurrent. Seule une goroutine peut détenir le verrou à la fois, garantissant que les sections critiques de votre code sont exécutées de manière atomique.
var counter int
var mutex sync.Mutex
func incrementCounter() {
mutex.Lock()
defer mutex.Unlock()
counter++
}
WaitGroups
Le type sync.WaitGroup
vous permet d'attendre qu'un ensemble de goroutines ait terminé avant de continuer. Cela est utile pour coordonner l'exécution de plusieurs goroutines.
var wg sync.WaitGroup
func doWork() {
defer wg.Done()
// Do some work
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go doWork()
}
wg.Wait()
// All goroutines have finished
}
Canaux (Channels)
Les canaux en Go sont un outil puissant pour communiquer entre les goroutines. Ils peuvent être utilisés pour transmettre des données, des signaux et des primitives de synchronisation entre des processus concurrents.
func producer(out chan<- int) {
out <- 42
close(out)
}
func consumer(in <-chan int) {
num := <-in
fmt.Println("Received:", num)
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}
En combinant ces modèles de concurrence et outils de synchronisation, vous pouvez écrire des programmes Go concurrentiels efficaces et fiables qui gèrent efficacement les ressources partagées et évitent les conditions de concurrence.