1. Exécutez chaque exemple : Ne vous contentez pas de lire le code. Tapez-le, exécutez-le et observez le comportement.⚠️ Comment s'y prendre pour cette série ?
2. Expérimentez et cassez des choses : Supprimez les mises en veille et voyez ce qui se passe, modifiez la taille des tampons de canal, modifiez le nombre de goroutines.
Casser des choses vous apprend comment elles fonctionnent
3. Raison concernant le comportement : Avant d'exécuter du code modifié, essayez de prédire le résultat. Lorsque vous constatez un comportement inattendu, faites une pause et réfléchissez à pourquoi. Contestez les explications.
4. Construire des modèles mentaux : Chaque visualisation représente un concept. Essayez de dessiner vos propres diagrammes pour le code modifié.
Dans notre article précédent, nous avons exploré le modèle de simultanéité Generator, les éléments constitutifs des autres modèles de simultanéité de Go. Vous pouvez le lire ici :
Voyons maintenant comment ces primitives se combinent pour former des modèles puissants qui résolvent des problèmes du monde réel.
Dans cet article, nous aborderons le Modèle de pipeline et essaierons de les visualiser. Alors préparons-nous car nous serons présents tout au long du processus.
Un pipeline est comme une chaîne de montage dans une usine, où chaque étape effectue une tâche spécifique sur les données et transmet le résultat à l'étape suivante.
Nous construisons des pipelines en connectant les goroutines avec des canaux, où chaque goroutine représente une étape qui reçoit les données, les traite et les envoie à l'étape suivante.
Implémentons un pipeline simple qui :
// Stage 1: Generate numbers func generate(nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out <- n } }() return out } // Stage 2: Square numbers func square(in <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for n := range in { out <- n * n } }() return out } // Stage 3: Print numbers func print(in <-chan int) { for n := range in { fmt.Printf("%d ", n) } fmt.Println() } func main() { // Connect the pipeline numbers := generate(2, 3, 4) // Stage 1 squares := square(numbers) // Stage 2 print(squares) // Stage 3 }
✏️ Octet rapide
<-chan int désigne un canal de réception uniquement.
Un canal de type <-chan int ne peut être utilisé que pour recevoir des valeurs, pas pour les envoyer. Ceci est utile pour appliquer des modèles de communication plus stricts et empêcher les écritures accidentelles sur le canal par le récepteur.chan int Cela désigne un canal bidirectionnel.
Un canal de type chan int peut être utilisé à la fois pour envoyer et recevoir des valeurs.
Allons-y et visualisons l'exemple ci-dessus :
Ici, vous pouvez voir que chacun des éléments constitutifs du pipeline est des goroutines suivant le modèle du générateur. Cela implique que dès que les données sont prêtes à n'importe quelle étape, l'étape suivante du pipeline peut commencer à les traiter contrairement au traitement séquentiel.
Les principes fondamentaux devraient être :
Mettons à jour notre code avec une gestion appropriée des erreurs.
type Result struct { Value int Err error } func generateWithError(nums ...int) <-chan Result { out := make(chan Result) go func() { defer close(out) for _, n := range nums { if n < 0 { out <- Result{Err: fmt.Errorf("negative number: %d", n)} return } out <- Result{Value: n} } }() return out } func squareWithError(in <-chan Result) <-chan Result { out := make(chan Result) go func() { defer close(out) for r := range in { if r.Err != nil { out <- r // Forward the error continue } out <- Result{Value: r.Value * r.Value} } }() return out } func main() { // Using pipeline with error handling for result := range squareWithError(generateWithError(2, -3, 4)) { if result.Err != nil { fmt.Printf("Error: %v\n", result.Err) continue } fmt.Printf("Result: %d\n", result.Value) } }
Prenons un exemple pour mieux comprendre, nous avons un workflow de traitement de données qui suit le modèle de pipeline comme indiqué ci-dessous.
? Chaque étape peut être développée, testée et modifiée indépendamment
? Les modifications apportées aux éléments internes d'une étape n'affectent pas les autres étapes
? Facile d'ajouter de nouvelles étapes ou de modifier celles existantes
? Séparation claire des préoccupations
Et le meilleur ? Nous pouvons exécuter plusieurs instances de chaque étape (travailleurs) pour des exigences plus simultanées comme ceci :
?? Hé, mais n'est-ce pas le Modèle de concurrence Fan-In et Fan-Out ?
Bingo ! Bonne prise là. Il s’agit en effet d’un modèle Fan-Out, Fan-In, qui est un type spécifique de modèle de pipeline. Nous allons en parler en détail dans notre prochain article, alors ne vous inquiétez pas ;)
traitement des images dans un pipeline
// Stage 1: Generate numbers func generate(nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out <- n } }() return out } // Stage 2: Square numbers func square(in <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for n := range in { out <- n * n } }() return out } // Stage 3: Print numbers func print(in <-chan int) { for n := range in { fmt.Printf("%d ", n) } fmt.Println() } func main() { // Connect the pipeline numbers := generate(2, 3, 4) // Stage 1 squares := square(numbers) // Stage 2 print(squares) // Stage 3 }
ou quelque chose d'aussi compliqué qu'un pipeline de traitement des journaux
Ce modèle est idéal pour les opérations liées au processeur où le travail peut être traité indépendamment. Le pipeline répartit le travail entre plusieurs nœuds de calcul, puis recombine les résultats. Ceci est particulièrement efficace lorsque :
Ce modèle permet de gérer les écarts de vitesse entre les étapes du pipeline. Le tampon agit comme un amortisseur, permettant aux étapes rapides d'avancer sans être bloquées par des étapes plus lentes. Ceci est utile lorsque :
Ce modèle optimise les opérations liées aux E/S en regroupant plusieurs éléments en un seul lot. Au lieu de traiter les éléments un par un, il les rassemble en groupes et les traite ensemble. Ceci est efficace lorsque :
Chacun de ces modèles peut être combiné selon les besoins. Par exemple, vous pouvez utiliser le traitement par lots avec mise à l'échelle horizontale, dans lequel plusieurs travailleurs traitent chacun des lots d'éléments. La clé est de comprendre vos goulots d'étranglement et de choisir le modèle approprié pour les résoudre.
Cela conclut notre plongée profonde dans le modèle Generator ! Dans la prochaine étape, nous explorerons le modèle de concurrence concurrentielle du pipeline, où nous verrons comment enchaîner nos générateurs pour créer de puissants flux de traitement de données.
Si vous avez trouvé cet article utile, si vous avez des questions ou si vous souhaitez partager vos propres expériences avec les générateurs, j'aimerais avoir de vos nouvelles dans les commentaires ci-dessous. Vos idées et vos questions contribuent à rendre ces explications encore meilleures pour tout le monde.
Si vous avez manqué le guide visuel de la goroutine et des chaînes de Golang, consultez-le ici :
Restez à l'écoute pour plus de modèles de concurrence Go ! ?
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!