Merci de m’être abonné

Introduction

Lorsque j’ai commencé à travailler avec les canaux de Go pour la première fois, j’ai fait l’erreur de penser les canaux comme une structure de données. J’ai vu les canaux comme une file d’attente qui fournissait un accès synchronisé automatique entre les goroutines. Cette compréhension structurelle m’a amené à écrire beaucoup de code concurrent mauvais et compliqué.

J’ai appris au fil du temps qu’il était préférable d’oublier la structure des canaux et de me concentrer sur leur comportement. Alors maintenant, en ce qui concerne les canaux, je pense à une chose: la signalisation. Un canal permet à une goroutine de signaler à une autre goroutine un événement particulier. La signalisation est au cœur de tout ce que vous devriez faire avec les canaux. Considérer les canaux comme un mécanisme de signalisation vous permettra d’écrire un meilleur code avec un comportement bien défini et plus précis.

Pour comprendre le fonctionnement de la signalisation, il faut comprendre ses trois attributs:

  • Garantie de livraison
  • État
  • Avec ou sans données

Ces trois attributs fonctionnent ensemble pour créer une philosophie de conception autour de la signalisation. Après avoir discuté de ces attributs, je fournirai un certain nombre d’exemples de code qui démontrent la signalisation avec ces attributs appliqués.

Garantie de livraison

La Garantie de livraison repose sur une question: « Ai-je besoin d’une garantie que le signal envoyé par un goroutine particulier a été reçu? »

En d’autres termes, étant donné cet exemple dans la liste 1:

Liste 1

01 go func() {02 p := <-ch // Receive03 }()0405 ch <- "paper" // Send

La goroutine émettrice a-t-elle besoin d’une garantie que le paper envoyé sur le canal sur la ligne 05 a été reçu par la goroutine sur la ligne 02 avant de continuer?

Sur la base de la réponse à cette question, vous saurez lequel des deux types de canaux utiliser: Non tamponné ou tamponné. Chaque canal fournit un comportement différent autour des garanties de livraison.

Figure 1: Garantie de livraison

 Garantie de Livraison

Les garanties sont importantes, et, si vous ne le pensez pas, j’ai une tonne de choses que je veux vous vendre. Bien sûr, j’essaie de faire une blague, mais tu ne deviens pas nerveux quand tu n’as pas de garanties dans la vie? Il est crucial de bien comprendre si vous avez besoin ou non d’une garantie lors de l’écriture de logiciels concurrents. En continuant, vous apprendrez à décider.

État

Le comportement d’un canal est directement influencé par son état actuel. L’état d’un canal peut être nul, ouvert ou fermé.

La liste 2 ci-dessous montre comment déclarer ou placer un canal dans chacun de ces trois états.

Inscription 2

// ** nil channel// A channel is in a nil state when it is declared to its zero valuevar ch chan string// A channel can be placed in a nil state by explicitly setting it to nil.ch = nil// ** open channel// A channel is in a open state when it's made using the built-in function make.ch := make(chan string) // ** closed channel// A channel is in a closed state when it's closed using the built-in function close.close(ch)

L’état détermine le comportement des opérations d’envoi et de réception.

Les signaux sont envoyés et reçus via un canal. Ne dites pas lecture/écriture car les canaux n’effectuent pas d’E/ S.

Figure 2 : État

 État

Lorsqu’un canal est dans un état nul, toute tentative d’envoi ou de réception sur le canal sera bloquée. Lorsqu’un canal est dans un état ouvert, des signaux peuvent être envoyés et reçus. Lorsqu’un canal est placé dans un état fermé, les signaux ne peuvent plus être envoyés mais il est toujours possible de recevoir des signaux.

Ces états fourniront les différents comportements dont vous avez besoin pour les différentes situations que vous rencontrez. Lorsque vous combinez État et Garantie de livraison, vous pouvez commencer à analyser les coûts / avantages que vous encourez à la suite de vos choix de conception. Dans de nombreux cas, vous pourrez également repérer rapidement les bugs simplement en lisant le code, car vous comprenez comment la chaîne va se comporter.

Avec et sans données

Le dernier attribut de signalisation à prendre en compte est de savoir si vous devez signaler avec ou sans données.

Vous signalez avec des données en effectuant un envoi sur un canal.

Inscription 3

01 ch <- "paper"

Lorsque vous signalez avec des données, c’est généralement parce que:

  • On demande à un goroutine de commencer une nouvelle tâche.
  • Une goroutine rapporte un résultat.

Vous signalez sans données en fermant un canal.

Inscription 4

01 close(ch)

Lorsque vous signalez sans données, c’est généralement parce que:

  • On dit à un goroutine d’arrêter ce qu’il fait.
  • A goroutine signale qu’ils sont terminés sans résultat.
  • Un goroutine signale qu’il a terminé le traitement et s’est arrêté.

Il existe des exceptions à ces règles, mais ce sont les principaux cas d’utilisation et ceux sur lesquels nous nous concentrerons dans cet article. Je considérerais les exceptions à ces règles comme une odeur de code initiale.

Un avantage de la signalisation sans données est qu’une seule goroutine peut signaler plusieurs goroutines à la fois. La signalisation avec des données est toujours un échange 1 à 1 entre goroutines.

Signalisation avec des données

Lorsque vous allez signaler avec des données, vous pouvez choisir trois options de configuration de canal en fonction du type de garantie dont vous avez besoin.

Figure 3 : Signalisation Avec Des Données

Les trois options de canal sont Non tamponnées, Tamponnées > 1 ou Tamponnées =1.

  • Garantie

    • Un canal non tamponné vous garantit qu’un signal envoyé a bien été reçu.
      • Parce que la réception du signal se Produit Avant la fin de l’émission du signal.
  • Aucune garantie

    • Un canal tamponné de taille > 1 ne vous garantit pas qu’un signal envoyé a été reçu.
      • Parce que l’envoi du signal se Produit Avant la fin de la réception du signal.
  • Garantie retardée

    • Un canal tamponné de taille = 1 vous donne une garantie retardée. Il peut garantir que le signal précédent qui a été envoyé a été reçu.
      • Parce que la Réception du Premier Signal, Se Produit Avant la fin de l’Émission du Deuxième Signal.

La taille du tampon ne doit jamais être un nombre aléatoire, Elle doit toujours être calculée pour une contrainte bien définie. Il n’y a pas d’infini dans l’informatique, tout doit avoir une contrainte bien définie, que ce soit le temps ou l’espace.

Signalisation sans données

La signalisation sans données est principalement réservée à l’annulation. Cela permet à une goroutine de signaler à une autre goroutine d’annuler ce qu’elle fait et de passer à autre chose. L’annulation peut être implémentée en utilisant à la fois des canaux Non mis en mémoire tampon et des canaux mis en mémoire tampon, mais l’utilisation d’un canal mis en mémoire tampon lorsqu’aucune donnée ne sera envoyée est une odeur de code.

Figure 4 : Signalisation Sans Données

La fonction intégrée close est utilisée pour signaler sans données. Comme expliqué ci-dessus dans la section État, vous pouvez toujours recevoir des signaux sur un canal fermé. En fait, toute réception sur un canal fermé ne se bloquera pas et l’opération de réception revient toujours.

Dans la plupart des cas, vous souhaitez utiliser le package de bibliothèque standard context pour implémenter la signalisation sans données. Le package context utilise un canal non tamponné en dessous pour que la signalisation et la fonction intégrée close signalent sans données.

Si vous choisissez d’utiliser votre propre canal pour l’annulation, plutôt que le package contextuel, votre canal doit être de type chan struct{}. C’est la manière idiomatique d’indiquer un canal utilisé uniquement pour la signalisation.

Scénarios

Avec ces attributs en place, la meilleure façon de mieux comprendre comment ils fonctionnent dans la pratique est de parcourir une série de scénarios de code. J’aime penser aux goroutines en tant que personnes lorsque je lis et écris du code basé sur des canaux. Cette visualisation aide vraiment, et je l’utiliserai comme aide ci-dessous.

Signal Avec Canaux sans tampon Garantis par les données

Lorsque vous devez savoir qu’un signal envoyé a été reçu, deux scénarios entrent en jeu. Ce sont Attendre la Tâche et Attendre le Résultat.

Scénario 1 – Attendez la tâche

Pensez à être un gestionnaire et à embaucher un nouvel employé. Dans ce scénario, vous souhaitez que votre nouvel employé effectue une tâche, mais il doit attendre que vous soyez prêt. C’est parce que vous devez leur remettre un morceau de papier avant qu’ils ne commencent.

Inscription 5
https://play.golang.org/p/BnVEHRCcdh

01 func waitForTask() {02 ch := make(chan string)0304 go func() {05 p := <-ch0607 // Employee performs work here.0809 // Employee is done and free to go.10 }()1112 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)1314 ch <- "paper"15 }

À la ligne 02 de la liste 5, un canal non tamponné est créé avec l’attribut que les données string seront envoyées avec le signal. Ensuite, sur la ligne 04, un employé est embauché et dit d’attendre votre signal sur la ligne 05 avant de faire son travail. La ligne 05 est le canal de réception, ce qui provoque le blocage de l’employé en attendant le morceau de papier que vous allez envoyer. Une fois le document reçu par l’employé, celui-ci effectue le travail, puis est terminé et libre de partir.

En tant que gestionnaire, vous travaillez en même temps que votre nouvel employé. Ainsi, après avoir embauché l’employé à la ligne 04, vous vous retrouvez (à la ligne 12) à faire ce que vous devez faire pour débloquer et signaler l’employé. Notez qu’on ne savait pas combien de temps il faudrait pour préparer ce morceau de papier que vous devez envoyer.

Finalement, vous êtes prêt à signaler l’employé. Sur la ligne 14, vous effectuez un signal avec des données, les données étant ce morceau de papier. Puisqu’un canal non tamponné est utilisé, vous obtenez une garantie que l’employé a reçu le papier une fois votre opération d’envoi terminée. La réception a lieu avant l’envoi.

Techniquement, tout ce que vous savez, c’est que l’employé a le papier au moment où votre opération d’envoi de canal se termine. Après les deux opérations de canal, le planificateur peut choisir d’exécuter n’importe quelle instruction qu’il souhaite. La ligne de code suivante exécutée par vous ou par l’employé n’est pas déterministe. Cela signifie que l’utilisation d’instructions d’impression peut vous tromper sur l’ordre des choses.

Scénario 2 – Attendez le résultat

Dans ce scénario suivant, les choses sont inversées. Cette fois, vous souhaitez que votre nouvel employé effectue une tâche immédiatement lorsqu’il est embauché, et vous devez attendre le résultat de son travail. Vous devez attendre car vous avez besoin du papier d’eux avant de pouvoir continuer.

Inscription 6
https://play.golang.org/p/VFAWHxIQTP

01 func waitForResult() {02 ch := make(chan string)0304 go func() {05 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)0607 ch <- "paper"0809 // Employee is done and free to go.10 }()1112 p := <-ch13 }

À la ligne 02 de la liste 6, un canal non tamponné est créé avec l’attribut que les données string seront envoyées avec le signal. Ensuite, sur la ligne 04, un employé est embauché et est immédiatement mis au travail. Après avoir embauché l’employé à la ligne 04, vous vous retrouvez ensuite à la ligne 12 en attente du rapport papier.

Une fois le travail terminé par l’employé à la ligne 05, il vous envoie le résultat à la ligne 07 en effectuant un canal d’envoi avec des données. Comme il s’agit d’un canal non tamponné, la réception a lieu avant l’envoi et l’employé est assuré que vous avez reçu le résultat. Une fois que l’employé a cette garantie, ils sont prêts et libres de partir. Dans ce scénario, vous n’avez aucune idée du temps qu’il faudra à l’employé pour terminer la tâche.

Coût/ avantage

Un canal non tamponné garantit qu’un signal envoyé a été reçu. C’est génial, mais rien n’est gratuit. Le coût de cette garantie est une latence inconnue. Dans le scénario d’attente de la tâche, l’employé n’a aucune idée du temps qu’il vous faudra pour envoyer ce papier. Dans le scénario d’attente de résultat, vous n’avez aucune idée du temps qu’il faudra à l’employé pour vous envoyer ce résultat.

Dans les deux scénarios, cette latence inconnue est quelque chose avec laquelle nous devons vivre car la garantie est requise. La logique ne fonctionne pas sans ce comportement garanti.

Signal Avec Données – Pas De Garantie – Canaux Tamponnés >1

Lorsque vous n’avez pas besoin de savoir qu’un signal envoyé a été reçu, ces deux scénarios entrent en jeu: Fan Out et Drop.

Un canal mis en mémoire tampon a un espace bien défini qui peut être utilisé pour stocker les données envoyées. Alors, comment décidez-vous de l’espace dont vous avez besoin? Répondez à ces questions:

  • Ai-je une quantité de travail bien définie à accomplir?
    • Combien de travail y a-t-il?
  • Si mon employé ne peut pas suivre, puis-je abandonner tout nouveau travail?
    • Combien de travail exceptionnel me met à la capacité?
  • Quel niveau de risque suis-je prêt à accepter si mon programme prend fin de façon inattendue?
    • Tout ce qui attend dans le tampon sera perdu.

Si ces questions n’ont pas de sens pour le comportement que vous modélisez, c’est une odeur de code que l’utilisation d’un canal tamponné supérieur à 1 est probablement fausse.

Scénario 1 – Sortie en éventail

Un modèle de sortie en éventail vous permet de lancer un nombre bien défini d’employés sur un problème qui travaillent simultanément. Puisque vous avez un employé pour chaque tâche, vous savez exactement combien de rapports vous recevrez. Vous pouvez vous assurer qu’il y a la bonne quantité d’espace dans votre boîte pour recevoir tous ces rapports. Cela a l’avantage que vos employés n’ont pas besoin d’attendre que vous soumettiez leur rapport. Ils doivent cependant chacun prendre un tour en plaçant le rapport dans votre boîte s’ils arrivent à la boîte au même moment ou presque.

Imaginez que vous êtes à nouveau le manager, mais cette fois, vous embauchez une équipe d’employés. Vous avez une tâche individuelle que vous souhaitez que chaque employé effectue. Lorsque chaque employé termine sa tâche, il doit vous fournir un rapport papier qui doit être placé dans votre boîte sur votre bureau.

Inscription 7
https://play.golang.org/p/8HIt2sabs_

01 func fanOut() {02 emps := 2003 ch := make(chan string, emps)0405 for e := 0; e < emps; e++ {06 go func() {07 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)08 ch <- "paper"09 }()10 }1112 for emps > 0 {13 p := <-ch14 fmt.Println(p)15 emps--16 }17 }

À la ligne 03 de la liste 7, un canal mis en mémoire tampon est créé avec l’attribut que les données string seront envoyées avec le signal. Cette fois, le canal est créé avec 20 tampons grâce à la variable emps déclarée à la ligne 02.

Entre les lignes 05 à 10, 20 employés sont embauchés et ils se mettent immédiatement au travail. Vous ne savez pas combien de temps chaque employé va prendre à la ligne 07. Puis sur la ligne 08, les employés envoient le rapport papier mais cette fois l’envoi ne bloque pas l’attente d’une réception. Comme il y a de la place dans la boîte pour chaque employé, l’envoi sur le canal est uniquement en concurrence avec d’autres employés qui souhaitent envoyer leur rapport au même moment ou presque.

Le code entre les lignes 12 à 16 est tout ce que vous. C’est là que vous attendez que les 20 employés terminent leur travail et envoient leur rapport. Sur la ligne 12, vous êtes dans une boucle et sur la ligne 13 vous êtes bloqué dans une réception de canal en attente de vos rapports. Une fois qu’un rapport est reçu, le rapport est imprimé à la ligne 14 et la variable de compteur local est décrémentée pour indiquer qu’un employé a terminé.

Scénario 2 – Drop

Un motif de drop vous permet de jeter du travail lorsque vos employés sont à pleine capacité. Cela a l’avantage de continuer à accepter le travail de vos clients et de ne jamais appliquer de contre-pression ou de latence dans l’acceptation de ce travail. La clé ici est de savoir quand vous êtes vraiment à votre capacité afin que vous ne vous engagiez pas sous ou sur la quantité de travail que vous tenterez de faire. Habituellement, les tests d’intégration ou les métriques sont ce dont vous avez besoin pour vous aider à identifier ce nombre.

Imaginez que vous êtes à nouveau le gestionnaire et que vous embauchez un seul employé pour faire le travail. Vous avez une tâche individuelle que vous souhaitez que l’employé effectue. Lorsque l’employé termine sa tâche, vous ne vous souciez pas de savoir qu’ils ont terminé. Tout ce qui est important est de savoir si vous pouvez ou non placer de nouveaux travaux dans la boîte. Si vous ne pouvez pas effectuer l’envoi, vous savez que votre boîte est pleine et que l’employé est à pleine capacité. À ce stade, le nouveau travail doit être jeté pour que les choses puissent continuer à bouger.

Inscription 8
https://play.golang.org/p/PhFUN5itiv

01 func selectDrop() {02 const cap = 503 ch := make(chan string, cap)0405 go func() {06 for p := range ch {07 fmt.Println("employee : received :", p)08 }09 }()1011 const work = 2012 for w := 0; w < work; w++ {13 select {14 case ch <- "paper":15 fmt.Println("manager : send ack")16 default:17 fmt.Println("manager : drop")18 }19 }2021 close(ch)22 }

À la ligne 03 de la liste 8, un canal mis en mémoire tampon est créé avec l’attribut que les données string seront envoyées avec le signal. Cette fois, le canal est créé avec 5 tampons grâce à la constante cap déclarée à la ligne 02.

Entre les lignes 05 à 09, un seul employé est embauché pour s’occuper du travail. A for range est utilisé pour la réception du canal. Chaque fois qu’un morceau de papier est reçu, il est traité à la ligne 07.

Entre les lignes 11 à 19, vous tentez d’envoyer 20 morceaux de papier à votre employé. Cette fois, une instruction select est utilisée pour effectuer l’envoi dans la première case de la ligne 14. Étant donné que la clause default est utilisée à l’intérieur de la select sur la ligne 16, si l’envoi va se bloquer car il n’y a plus de place dans le tampon, l’envoi est abandonné en exécutant la ligne 17.

Enfin sur la ligne 21, la fonction intégrée close est appelée contre le canal. Cela signalera sans données à l’employé qu’il a terminé et qu’il est libre de partir une fois qu’il a terminé son travail assigné..

Coût/bénéfice

Un canal tamponné supérieur à 1 ne garantit pas qu’un signal envoyé soit jamais reçu. Il y a un avantage à s’éloigner de cette garantie, qui est la latence réduite ou nulle dans la communication entre deux goroutines. Dans le scénario de sortie, il existe un espace tampon pour chaque employé qui enverra un rapport. Dans le scénario de chute, le tampon est mesuré pour la capacité et si la capacité est atteinte, le travail est abandonné afin que les choses puissent continuer à bouger.

Dans les deux options, ce manque de garantie est quelque chose avec lequel nous devons vivre car la réduction de la latence est plus importante. L’exigence d’une latence nulle à minimale ne pose pas de problème à la logique globale du système.

Signal Avec Canal 1 de Garantie de données Retardées

Lorsqu’il est nécessaire de savoir si le signal précédent qui a été envoyé a été reçu avant d’envoyer un nouveau signal, le scénario d’attente des tâches entre en jeu.

Scénario 1 – Attendez les tâches

Dans ce scénario, vous avez un nouvel employé, mais ils vont faire plus qu’une seule tâche. Vous allez les nourrir de nombreuses tâches, l’une après l’autre. Cependant, ils doivent terminer chaque tâche individuelle avant de pouvoir en commencer une nouvelle. Comme ils ne peuvent travailler que sur une seule tâche à la fois, il pourrait y avoir des problèmes de latence entre le transfert du travail. Si la latence pouvait être réduite sans perdre la garantie que l’employé travaille sur la tâche suivante, cela pourrait aider.

C’est là qu’un canal tamponné de 1 présente des avantages. Si tout se déroule au rythme prévu entre vous et l’employé, aucun de vous n’aura besoin d’attendre l’autre. Chaque fois que vous envoyez un morceau de papier, le tampon est vide. Chaque fois que votre employé cherche plus de travail, le tampon est plein. C’est une symétrie parfaite du flux de travail.

La meilleure partie est la suivante. Si à tout moment vous essayez d’envoyer un morceau de papier et que vous ne pouvez pas parce que le tampon est plein, vous savez que votre employé a un problème et vous arrêtez. C’est là que cette garantie retardée entre en jeu. Lorsque le tampon est vide et que vous effectuez l’envoi, vous avez la garantie que votre employé a pris le dernier travail que vous avez envoyé. Si vous effectuez l’envoi et que vous ne pouvez pas, vous avez la garantie qu’ils ne l’ont pas fait.

Liste 9
https://play.golang.org/p/4pcuKCcAK3

01 func waitForTasks() {02 ch := make(chan string, 1)0304 go func() {05 for p := range ch {06 fmt.Println("employee : working :", p)07 }08 }()0910 const work = 1011 for w := 0; w < work; w++ {12 ch <- "paper"13 }1415 close(ch)16 }

À la ligne 02 de la liste 9, un canal mis en mémoire tampon de taille 1 est créé avec l’attribut que les données string seront envoyées avec le signal. Entre les lignes 04 à 08, un seul employé est embauché pour s’occuper du travail. A for range est utilisé pour la réception du canal. Chaque fois qu’un morceau de papier est reçu, il est traité à la ligne 06.

Entre les lignes 10 à 13, vous commencez à envoyer vos tâches à l’employé. Si votre employé peut fonctionner aussi vite que vous pouvez envoyer, la latence entre vous deux est réduite. Mais à chaque envoi que vous effectuez avec succès, vous avez la garantie que le dernier travail que vous avez soumis est en cours de travail.

Enfin sur la ligne 15, la fonction intégrée close est appelée contre le canal. Cela signalera sans données à l’employé qu’ils sont terminés et libres de partir. Cependant, le dernier travail que vous avez soumis sera reçu (rincé) avant la fin du for range.

Signal Sans Contexte de données

Dans ce dernier scénario, vous verrez comment vous pouvez annuler une goroutine en cours d’exécution en utilisant une valeur Context du package context. Tout cela fonctionne en tirant parti d’un canal non tamponné qui est fermé pour effectuer un signal sans données.

Vous êtes le manager une dernière fois et vous embauchez un seul employé pour faire le travail. Cette fois, vous n’êtes pas prêt à attendre un laps de temps inconnu pour que l’employé termine. Vous êtes sur une date limite discrète et si l’employé ne termine pas à temps, vous n’êtes pas prêt à attendre.

Inscription 10
https://play.golang.org/p/6GQbN5Z7vC

01 func withTimeout() {02 duration := 50 * time.Millisecond0304 ctx, cancel := context.WithTimeout(context.Background(), duration)05 defer cancel()0607 ch := make(chan string, 1)0809 go func() {10 time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)11 ch <- "paper"12 }()1314 select {15 case p := <-ch:16 fmt.Println("work complete", p)1718 case <-ctx.Done():19 fmt.Println("moving on")20 }21 }

À la ligne 02 de la liste 10, une valeur de durée est déclarée qui représente le temps que l’employé devra terminer la tâche. Cette valeur est utilisée à la ligne 04 pour créer une valeur context.Context avec un délai d’expiration de 50 millisecondes. La fonction WithTimeout du package context renvoie une valeur Context et une fonction d’annulation.

Le package context crée une goroutine qui fermera le canal non tamponné associé à la valeur Context une fois la durée atteinte. Vous êtes responsable de l’appel de la fonction cancel indépendamment de la façon dont les choses se passent. Cela nettoiera les choses qui ont été créées pour le Context. Il est correct que la fonction cancel soit appelée plus d’une fois.

Sur la ligne 05, la fonction cancel est différée pour être exécutée une fois cette fonction terminée. Sur la ligne 07, un canal tampon de 1 est créé, qui va être utilisé par l’employé pour vous envoyer le résultat de son travail. Ensuite, sur les lignes 09 à 12, l’employé est embauché et immédiatement mis au travail. Vous n’avez aucune idée du temps qu’il faudra à l’employé pour terminer.

Entre les lignes 14 à 20, vous utilisez l’instruction select pour recevoir sur deux canaux. La réception sur la ligne 15, vous attendez que l’employé vous envoie son résultat. La réception sur la ligne 18, vous attendez de voir si le paquet context va signaler que les 50 millisecondes sont en hausse. Quel que soit le signal que vous recevrez en premier sera celui traité.

Un aspect important de cet algorithme est l’utilisation du canal tamponné de 1. Si l’employé ne termine pas à temps, vous passez à autre chose sans lui donner de préavis. Du point de vue des employés, ils vous enverront toujours le rapport à la ligne 11 et ils sont aveugles si vous êtes là ou non pour le recevoir. Si vous utilisez un canal sans tampon, l’employé bloquera pour toujours en essayant de vous envoyer le rapport si vous continuez. Cela créerait une fuite de goroutine. Ainsi, un canal tamponné de 1 est utilisé pour empêcher cela de se produire.

Conclusion

Les attributs de signalisation autour des garanties, de l’état du canal et de l’envoi sont importants à connaître et à comprendre lors de l’utilisation des canaux (ou de la concurrence). Ils vous aideront à implémenter le meilleur comportement dont vous avez besoin pour les programmes et algorithmes simultanés que vous écrivez. Ils vous aideront à trouver des bugs et à renifler du code potentiellement mauvais.

Dans cet article, j’ai partagé quelques exemples de programmes qui montrent comment les attributs de signalisation fonctionnent dans différents scénarios. Il y a des exceptions à chaque règle, mais ces modèles sont une bonne base pour commencer.

Examinez ces grandes lignes comme un résumé du moment et de la façon de réfléchir et d’utiliser efficacement les canaux:

Mécanique du langage

  • Utilisez des canaux pour orchestrer et coordonner les goroutines.
    • Concentrez-vous sur les attributs de signalisation et non sur le partage des données.
    • Signalisation avec ou sans données.
    • Interrogez leur utilisation pour synchroniser l’accès à l’état partagé.
      • Il y a des cas où les canaux peuvent être plus simples pour cela mais au départ questionner.
  • Canaux non tamponnés:
    • La réception se produit avant l’envoi.
    • Avantage: Garantie à 100% que le signal a été reçu.
    • Coût: Latence inconnue sur le moment où le signal sera reçu.
  • Canaux tamponnés:
    • L’envoi se produit avant la réception.
    • Avantage: Réduisez la latence de blocage entre la signalisation.
    • Coût: Aucune garantie lorsque le signal a été reçu.
      • Plus le tampon est grand, moins il y a de garantie.
      • Un tampon de 1 peut vous donner un envoi retardé de garantie.
  • Canaux de fermeture:
    • La fermeture se produit avant la réception (comme Mise en mémoire tampon).
    • Signalisation sans données.
    • Parfait pour signaler les annulations et les délais.
  • canaux nuls: Bloc d’envoi et de réception
    • .
    • Désactivez la signalisation
    • Parfait pour les arrêts de limitation de débit ou de courte durée.

Philosophie de conception

  • Si un envoi donné sur un canal PEUT entraîner le blocage de la goroutine d’envoi :
    • Interdit d’utiliser un canal tamponné supérieur à 1.
      • Les tampons supérieurs à 1 doivent avoir des raisons/mesures.
    • Doit savoir ce qui se passe lorsque l’envoi de goroutine bloque.
  • Si un envoi donné sur un canal NE provoque PAS le blocage de la goroutine d’envoi:
    • Vous avez le nombre exact de tampons pour chaque envoi.
      • Modèle de sortie en éventail
    • Vous avez le tampon mesuré pour la capacité maximale.
      • Motif de chute
  • Moins c’est plus avec les tampons.
    • Ne pensez pas aux performances lorsque vous pensez aux tampons.Les tampons
    • peuvent aider à réduire la latence de blocage entre la signalisation.
      • Réduire la latence de blocage vers zéro ne signifie pas nécessairement un meilleur débit.
      • Si un tampon d’un vous donne un débit suffisant, conservez-le.
      • Tampons de questions qui sont plus grands qu’un et mesurent la taille.
      • Trouvez le tampon le plus petit possible qui fournit un débit suffisant.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.