ProgrammationDéveloppeur Go

Décrivez la spécificité de l'utilisation de time.Sleep et time.After en Go : quelle est la différence, quand appliquer quoi et quels sont les pièges dans l'utilisation de time.Sleep dans des programmes concurrents ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question:

Depuis le début, le langage Go a fourni le paquet time, au sein duquel se trouvent les principales fonctions pour gérer le temps : time.Sleep et time.After. Contrairement aux langages avec un sommeil système (System.sleep), Go implémente des minuteries asynchrones via ses primitives, ce qui est important pour le travail multi-thread.

Problème:

Souvent, les développeurs utilisent mal time.Sleep pour réaliser des pauses entre les tâches, ce qui est indésirable dans les programmes concurrents. Dans la paradigme Go, il est plus correct de gérer l'attente d'événements via des canaux et time.After pour l'intégration avec select/canaux.

Solution:

time.Sleep(d) bloque la goroutine actuelle pendant d temps, c'est un « sommeil » direct. time.After(d) retourne un canal sur lequel un événement-temps apparaîtra après d. La dernière option est beaucoup plus flexible dans select par rapport aux canaux, pratique pour les attentes interrompues et les délais.

Exemple de code :

ch := make(chan struct{}) go func() { time.Sleep(2 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("fait") case <-time.After(1 * time.Second): fmt.Println("délai") }

Caractéristiques clés :

  • time.After se combine parfaitement avec select pour réaliser des délais.
  • time.Sleep bloque uniquement la goroutine actuelle, et non le thread/processus.
  • time.After crée à chaque fois un nouveau canal, les anciens ne sont pas libérés jusqu'à la lecture de la valeur.

Questions piégées.

Peut-on utiliser time.Sleep pour bloquer l'exécution de tout le programme ?

Non, time.Sleep « endort » seulement une goroutine, les autres continuent à travailler.

Le time.After peut-il entraîner des fuites de mémoire si le canal n'est pas lu ?

Oui, le minuteur reste actif tant que la valeur n'est pas lue — s'il n'y a pas de lecture, le ramasse-miettes ne supprimera pas l'objet.

Exemple de code :

func leak() { for { _ = time.After(time.Hour) } }

Comment annuler correctement l’attente par time.After en cas d’absence d’événement ?

Il est préférable d'utiliser time.Timer et d'arrêter manuellement, si besoin, d'annuler l'attente avant la date limite :

t := time.NewTimer(time.Minute) if done { t.Stop() }

Erreurs typiques et anti-modèles

  • Utiliser time.Sleep dans des goroutines de travail au lieu de canaux et select.
  • Ne pas lire de time.After, ce qui entraîne des fuites de minuteurs.
  • Créer time.After dans une boucle, sans avoir le temps de consommer les valeurs.

Exemple de la vie

Cas négatif

La goroutine attend un signal du travail, et en cas de délai fait ceci :

time.Sleep(10*time.Second) doSomething()

Avantages :

  • Simple d’ajouter une pause temporaire.

Inconvénients :

  • Si le signal a déjà été reçu ou s’il n’y a pas besoin de travailler — sommeil superflu, risque de bugs de synchronisation.
  • Tout est irréversible, difficile de « réveiller » si l’annulation s’est produite avant le délai.

Cas positif

Le code est construit via select et time.After :

select { case <-workSignal: // Exécuter case <-time.After(10 * time.Second): // Faire selon le délai }

Avantages :

  • L'attente et le délai sont interchangeables, plus facile de simuler une annulation.
  • Minimisation des temps d'arrêt.

Inconvénients :

  • Un peu plus difficile à lire, nécessite une compréhension du fonctionnement de select et des canaux.