Travailler avec les files d'attente de tâches en Go : pour débutants
Les files d'attente de tâches (task queues) sont l'un des patrons fondamentaux du développement moderne. En Go (Golang), ce mécanisme est implémenté de manière particulièrement élégante grâce aux primitives de concurrence intégrées : les goroutines et les canaux. Dans cet article, nous allons voir comment créer des files d'attente de tâches en Go, en commençant par des exemples simples et en terminant par des techniques avancées.
Qu'est-ce qu'une file d'attente de tâches et pourquoi en a-t-on besoin ?
Une file d'attente de tâches est une structure de données fonctionnant selon le principe FIFO (First In, First Out — « premier arrivé, premier sorti »). Imaginez une file d'attente à la caisse : le premier client est servi, puis le second, et ainsi de suite. En programmation, cela permet de :
- Séparer l'arrivée des tâches et leur traitement
- Limiter le nombre d'opérations exécutées simultanément
- Répartir uniformément la charge entre les gestionnaires
- Traiter les tâches en arrière-plan sans bloquer le flux principal
Go est idéal pour implémenter des files d'attente grâce à son modèle de concurrence. Contrairement à de nombreux langages qui nécessitent une bibliothèque externe (comme RabbitMQ ou Redis), en Go, vous pouvez créer une simple file d'attente de tâches avec seulement quelques lignes de code.
Création d'une file d'attente de tâches simple avec les canaux Go
La façon la plus simple d'implémenter une file d'attente de tâches est d'utiliser un canal tamponné. Regardons un exemple de base :
package main
import ( "fmt" "time")
func worker(tasks <-chan string, id int) { for task := range tasks { fmt.Printf("Travailleur %d a commencé la tâche : %s", id, task) time.Sleep(1 * time.Second) // Simulation de travail fmt.Printf("Travailleur %d a terminé la tâche : %s", id, task) }}
func main() { tasks := make(chan string, 5) // Canal tamponné pour 5 tâches
// Lancement de 2 travailleurs for i := 1; i <= 2; i++ { go worker(tasks, i) }
// Envoi des tâches dans la file d'attente for i := 1; i <= 10; i++ { tasks <- fmt.Sprintf("Tâche n°%d", i) }
close(tasks) // Fermeture du canal après l'envoi de toutes les tâches time.Sleep(2 * time.Second) // Temps pour terminer}Dans cet exemple :
- Le canal
tasksfait office de file d'attente - Deux travailleurs (goroutines) écoutent constamment le canal et traitent les tâches
- Un tampon de taille 5 permet d'envoyer jusqu'à 5 tâches sans traitement immédiat
Patron Worker Pool : traitement évolutif
Le Worker Pool (pool de travailleurs) est une évolution de la file d'attente simple. Au lieu d'un nombre fixe de travailleurs, nous créons un pool qui peut être mis à l'échelle dynamiquement. Ce patron est particulièrement utile pour :
- Le traitement des requêtes HTTP
- Le traitement par lots de données
- Le travail avec des API externes (limitation du nombre de requêtes simultanées)
Voici une implémentation plus avancée utilisant une structure :
package main
import ( "fmt" "sync" "time")
type Task struct { ID int Payload string}
type WorkerPool struct { tasks chan Task results chan string wg sync.WaitGroup workers int}
func NewWorkerPool(workers int) *WorkerPool { return &WorkerPool{ tasks: make(chan Task, 100), results: make(chan string, 100), workers: workers, }}
func (wp *WorkerPool) Start() { for i := 1; i <= wp.workers; i++ { wp.wg.Add(1) go wp.worker(i) }}
func (wp *WorkerPool) worker(id int) { defer wp.wg.Done() for task := range wp.tasks { result := fmt.Sprintf("Travailleur %d a exécuté la tâche %d : %s", id, task.ID, task.Payload) time.Sleep(500 * time.Millisecond) // Simulation de travail wp.results <- result }}
func (wp *WorkerPool) AddTask(task Task) { wp.tasks <- task}\