1. Qu'est-ce qu'une goroutine et en quoi differe-t-elle d'un thread OS ?
Une goroutine est un thread leger gere par le runtime Go, pas par le systeme d'exploitation. Elle demarre avec seulement 2 Ko de pile (contre 1-8 Mo pour un thread OS) et grandit dynamiquement. Le scheduler Go utilise le modele M:N : M goroutines sont multiplexees sur N threads OS. On peut lancer des millions de goroutines sur une machine modeste. Creation 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, operations sur les channels, et depuis Go 1.14, preemption asynchrone pour eviter les goroutines qui monopolisent un thread.
2. Expliquez le fonctionnement des channels en Go.
Les channels sont le mecanisme de communication entre goroutines, implementant le principe CSP (Communicating Sequential Processes). Creation 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 reception (valeur := <-ch) sont des operations bloquantes sur un channel non-buffered. Le select permet d'attendre sur plusieurs channels simultanement. Bonnes pratiques : fermer un channel cote emetteur avec close(), utiliser le pattern done-channel pour la signalisation, et le range sur un channel pour iterer jusqu'a 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 implementant l'interface error (methode Error() string). Convention : retourner l'erreur comme derniere valeur de retour. Le pattern if err != nil est omni-present. Depuis Go 1.13 : errors.Is() pour comparer avec une erreur specifique, errors.As() pour extraire un type d'erreur, et fmt.Errorf avec %w pour wrapper les erreurs et conserver la chaine. panic/recover existe mais est reserve aux situations vraiment exceptionnelles (pas pour le flux de controle normal). Creer des types d'erreurs personnalises 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 possede toutes les methodes requises, sans declaration 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 plutot que l'heritage. Bonnes pratiques : interfaces petites (io.Reader n'a qu'une methode), accepter des interfaces et retourner des types concrets, definir 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'ecrire des fonctions et types parametres 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 personnalisees. 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 specialisation, pas de methodes generiques sur les types, et le compilateur monomorphise (genere du code pour chaque type utilise).
6. Comment gerer la concurrence et eviter les race conditions ?
Go offre plusieurs mecanismes : channels pour la communication (prefere), sync.Mutex/RWMutex pour proteger l'acces concurrent aux donnees 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 a l'execution. Patterns courants : fan-in/fan-out pour paralleliser le traitement, worker pool avec un channel de taches, context pour la propagation de l'annulation et des timeouts. Principe fondamental : ne pas communiquer en partageant la memoire, partager la memoire 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 a travers les goroutines. Quatre constructeurs : context.Background() (racine), context.WithCancel() (annulation manuelle), context.WithTimeout() (annulation apres un delai), context.WithDeadline() (annulation a 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 requetes 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 maniere idiomatique ?
Le standard layout evolue mais les principes restent : cmd/ pour les points d'entree (main packages), internal/ pour le code prive au module (non-importable), pkg/ (optionnel) pour le code exportable. Organiser par domaine fonctionnel plutot que par couche technique. Eviter les packages generiques (utils, helpers, common). Un package doit avoir une responsabilite claire. Les tests sont dans le meme repertoire que le code (_test.go). Les interfaces definies cote consommateur. Go modules (go.mod) pour la gestion des dependances. 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 parallele 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'executer. Le parametre GOGC (defaut 100) controle le ratio de declenchement : une valeur plus haute reduit la frequence de collection mais augmente l'utilisation memoire. Depuis Go 1.19, GOMEMLIMIT permet de fixer un plafond memoire pour un meilleur controle.
10. Comment tester efficacement en Go ?
Le package testing est integre. Fichiers *_test.go avec fonctions TestXxx(t *testing.T). Table-driven tests : definir un slice de cas de test et iterer dessus, pattern idiomatique en Go. t.Run() pour les sous-tests nommes. 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'integration longs.