1. Qu'est-ce qu'une goroutine et en quoi differe-t-elle d'un thread OS ?
Une goroutine est un thread léger gere par le runtime Go, pas par le système d'exploitation. Elle démarre avec seulement 2 Ko de pile (contre 1-8 Mo pour un thread OS) et grandit dynamiquement. Le scheduler Go utilise le modèle M:N : M goroutines sont multiplexees sur N threads OS. On peut lancer des millions de goroutines sur une machine modeste. Création simple avec le mot-cle go devant un appel de fonction. Le scheduler Go est cooperatif avec des points de preemption aux appels de fonctions, opérations sur les channels, et depuis Go 1.14, preemption asynchrone pour éviter les goroutines qui monopolisent un thread.
2. Expliquez le fonctionnement des channels en Go.
Les channels sont le mécanisme de communication entre goroutines, implémentant le principe CSP (Communicating Sequential Processes). Création avec make(chan Type) pour un channel non-buffered (synchrone) ou make(chan Type, n) pour un channel buffered. L'envoi (ch <- valeur) et la réception (valeur := <-ch) sont des opérations bloquantes sur un channel non-buffered. Le select permet d'attendre sur plusieurs channels simultanément. Bonnes pratiques : fermer un channel cote emetteur avec close(), utiliser le pattern done-channel pour la signalisation, et le range sur un channel pour itérer jusqu'à sa fermeture. Ne jamais envoyer sur un channel ferme (panic).
3. Comment fonctionne la gestion des erreurs en Go ?
Go n'a pas d'exceptions. Les erreurs sont des valeurs implémentant l'interface error (méthode Error() string). Convention : retourner l'erreur comme dernière valeur de retour. Le pattern if err != nil est omni-present. Depuis Go 1.13 : errors.Is() pour comparer avec une erreur spécifique, errors.As() pour extraire un type d'erreur, et fmt.Errorf avec %w pour wrapper les erreurs et conserver la chaîne. panic/recover existe mais est reserve aux situations vraiment exceptionnelles (pas pour le flux de contrôle normal). Créer des types d'erreurs personnalisés pour enrichir le contexte et faciliter le traitement par les appelants.
4. Qu'est-ce qu'une interface en Go et comment fonctionne le duck typing ?
Les interfaces en Go sont implicites : un type implemente une interface s'il possède toutes les méthodes requises, sans déclaration explicite. C'est du structural typing (duck typing). L'interface vide interface{} (ou any depuis Go 1.18) accepte n'importe quel type. Les interfaces encouragent la composition plutôt que l'héritage. Bonnes pratiques : interfaces petites (io.Reader n'a qu'une méthode), accepter des interfaces et retourner des types concrets, définir l'interface cote consommateur pas cote producteur. Le type assertion (val, ok := i.(Type)) et le type switch permettent de travailler avec les interfaces dynamiquement.
5. Expliquez les generics en Go (introduits en Go 1.18).
Les generics permettent d'écrire des fonctions et types paramètres par des types. Syntaxe : func Map[T any, U any](s []T, f func(T) U) []U. Les contraintes de type limitent les types acceptes : comparable pour les types comparables, constraints.Ordered pour les types ordonnables, ou des interfaces personnalisées. L'union de types utilise le pipe : type Number interface { int | float64 }. Les generics evitent la duplication de code et les assertions de type sur interface{}. Limites : pas de spécialisation, pas de méthodes génériques sur les types, et le compilateur monomorphise (genere du code pour chaque type utilise).
6. Comment gérer la concurrence et éviter les race conditions ?
Go offre plusieurs mécanismes : channels pour la communication (préfère), sync.Mutex/RWMutex pour protéger l'accès concurrent aux données partagees, sync.WaitGroup pour attendre un groupe de goroutines, sync.Once pour l'initialisation unique, sync.Map pour les maps concurrentes. L'outil go run -race detecte les race conditions à l'exécution. Patterns courants : fan-in/fan-out pour paralleliser le traitement, worker pool avec un channel de tâches, context pour la propagation de l'annulation et des timeouts. Principe fondamental : ne pas communiquer en partageant la mémoire, partager la mémoire en communiquant.
7. Qu'est-ce que le context package et comment l'utiliser ?
Le package context gere la propagation de deadlines, signaux d'annulation et valeurs request-scoped à travers les goroutines. Quatre constructeurs : context.Background() (racine), context.WithCancel() (annulation manuelle), context.WithTimeout() (annulation après un délai), context.WithDeadline() (annulation à une heure precise). Passe en premier argument des fonctions (convention). Le context parent annule automatiquement ses enfants. Utilisation : timeout sur les appels HTTP, annulation de requêtes longues, propagation de metadata (request ID, user info). Ne jamais stocker le context dans une struct, toujours le passer explicitement.
8. Comment structurer un projet Go de manière idiomatique ?
Le standard layout evolue mais les principes restent : cmd/ pour les points d'entrée (main packages), internal/ pour le code prive au module (non-importable), pkg/ (optionnel) pour le code exportable. Organiser par domaine fonctionnel plutôt que par couche technique. Éviter les packages génériques (utils, helpers, common). Un package doit avoir une responsabilité claire. Les tests sont dans le même répertoire que le code (_test.go). Les interfaces définies cote consommateur. Go modules (go.mod) pour la gestion des dépendances. Utiliser make ou des outils comme mage/task pour les scripts de build.
9. Expliquez le fonctionnement du garbage collector de Go.
Le GC de Go est un collecteur concurrent, tri-color, mark-and-sweep. Tri-color : les objets sont blancs (non visites), gris (visites mais enfants non verifies), noirs (visites, enfants verifies). Le GC fonctionne en parallèle avec l'application (faible pause, objectif <1ms). Il utilise une write barrier pour maintenir l'invariant tri-color pendant que le programme continue de s'exécuter. Le paramètre GOGC (défaut 100) contrôle le ratio de declenchement : une valeur plus haute réduit la fréquence de collection mais augmente l'utilisation mémoire. Depuis Go 1.19, GOMEMLIMIT permet de fixer un plafond mémoire pour un meilleur contrôle.
10. Comment tester efficacement en Go ?
Le package testing est intégré. Fichiers *_test.go avec fonctions TestXxx(t *testing.T). Table-driven tests : définir un slice de cas de test et itérer dessus, pattern idiomatique en Go. t.Run() pour les sous-tests nommés. Benchmarks avec BenchmarkXxx(b *testing.B). t.Parallel() pour les tests paralleles. Mocking : utiliser les interfaces pour injecter des mocks (pas besoin de framework). httptest pour tester les handlers HTTP. testify (tiers) ajoute assertions et mocks. Le flag -race detecte les race conditions. -cover mesure la couverture. -short permet de distinguer les tests rapides des tests d'intégration longs.