Vielen Dank für Ihr Abonnement

Einführung

Als ich anfing, zum ersten Mal mit den Kanälen von Go zu arbeiten, habe ich den Fehler gemacht, Kanäle als Datenstruktur zu betrachten. Ich sah Channels als eine Warteschlange, die einen automatischen synchronisierten Zugriff zwischen Goroutinen ermöglichte. Dieses strukturelle Verständnis veranlasste mich, viel schlechten und komplizierten gleichzeitigen Code zu schreiben.

Ich habe im Laufe der Zeit gelernt, dass es am besten ist, die Struktur der Kanäle zu vergessen und sich darauf zu konzentrieren, wie sie sich verhalten. Wenn es also um Kanäle geht, denke ich an eine Sache: Signalisierung. Ein Kanal ermöglicht es einer Goroutine, einer anderen Goroutine ein bestimmtes Ereignis zu signalisieren. Signalisierung ist der Kern von allem, was Sie mit Kanälen tun sollten. Wenn Sie Kanäle als Signalmechanismus betrachten, können Sie besseren Code mit genau definiertem und präziserem Verhalten schreiben.

Um zu verstehen, wie Signalisierung funktioniert, müssen wir ihre drei Attribute verstehen:

  • Liefergarantie
  • Zustand
  • Mit oder ohne Daten

Diese drei Attribute bilden zusammen eine Designphilosophie rund um die Signalisierung. Nachdem ich diese Attribute besprochen habe, werde ich eine Reihe von Codebeispielen bereitstellen, die die Signalisierung mit diesen Attributen demonstrieren.

Zustellgarantie

Die Zustellgarantie basiert auf einer Frage: „Brauche ich eine Garantie, dass das von einer bestimmten Goroutine gesendete Signal empfangen wurde?“

Mit anderen Worten, angesichts dieses Beispiels in listing 1:

Listing 1

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

Benötigt die sendende Goroutine eine Garantie, dass die paper, die über den Kanal in Zeile 05 gesendet wird, von der Goroutine in Zeile 02 empfangen wurde, bevor sie fortfahren?

Basierend auf der Antwort auf diese Frage wissen Sie, welche der beiden Arten von Kanälen verwendet werden sollen: Ungepuffert oder gepuffert. Jeder Kanal bietet ein anderes Verhalten in Bezug auf Zustellgarantien.

Abbildung 1: Liefergarantie

Liefergarantie

Garantien sind wichtig, und wenn Sie dies nicht glauben, habe ich eine Menge Dinge, die ich Ihnen verkaufen möchte. Natürlich versuche ich einen Witz zu machen, aber wirst du nicht nervös, wenn du keine Garantien im Leben hast? Ein gutes Verständnis dafür, ob Sie eine Garantie benötigen oder nicht, ist beim Schreiben von gleichzeitiger Software von entscheidender Bedeutung. Während wir fortfahren, werden Sie lernen, wie Sie sich entscheiden.

State

Das Verhalten eines Kanals wird direkt von seinem aktuellen Status beeinflusst. Der Zustand eines Kanals kann null, offen oder geschlossen sein.

Listing 2 unten zeigt, wie ein Kanal in jedem dieser drei Zustände deklariert oder platziert wird.

Auflistung 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)

Der Status bestimmt, wie sich die Sende- und Empfangsoperationen verhalten.

Signale werden über einen Kanal gesendet und empfangen.

Abbildung 2: Status

Status

Wenn sich ein Kanal im Status nil befindet, werden alle Sende- oder Empfangsversuche auf dem Kanal blockiert. Wenn sich ein Kanal in einem offenen Zustand befindet, können Signale gesendet und empfangen werden. Wenn ein Kanal in einen geschlossenen Zustand versetzt wird, können keine Signale mehr gesendet werden, aber es ist immer noch möglich, Signale zu empfangen.

Diese Zustände stellen die verschiedenen Verhaltensweisen bereit, die Sie für die verschiedenen Situationen benötigen, denen Sie begegnen. Wenn Sie Zustand mit Liefergarantie kombinieren, können Sie beginnen, die Kosten / Nutzen zu analysieren, die Ihnen aufgrund Ihrer Designentscheidungen entstehen. In vielen Fällen können Sie Fehler auch schnell erkennen, indem Sie einfach den Code lesen, da Sie verstehen, wie sich der Kanal verhalten wird.

Mit und ohne Daten

Das letzte Signalattribut, das berücksichtigt werden muss, ist, ob Sie mit oder ohne Daten signalisieren müssen.

Sie signalisieren mit Daten, indem Sie einen Send auf einem Kanal ausführen.

Auflistung 3

01 ch <- "paper"

Wenn Sie mit Daten signalisieren, liegt dies normalerweise daran:

  • Eine Goroutine wird aufgefordert, eine neue Aufgabe zu starten.
  • Eine Goroutine meldet ein Ergebnis.

Sie signalisieren ohne Daten, indem Sie einen Kanal schließen.

Auflistung 4

01 close(ch)

Wenn Sie ohne Daten signalisieren, liegt dies normalerweise daran:

  • Einer Goroutine wird gesagt, sie solle aufhören, was sie tun.
  • Eine Goroutine meldet, dass sie ohne Ergebnis fertig sind.
  • Eine Goroutine meldet, dass die Verarbeitung abgeschlossen und heruntergefahren wurde.

Es gibt Ausnahmen von diesen Regeln, aber dies sind die wichtigsten Anwendungsfälle, auf die wir uns in diesem Beitrag konzentrieren werden. Ich würde Ausnahmen von diesen Regeln als einen anfänglichen Code-Geruch betrachten.

Ein Vorteil der Signalisierung ohne Daten ist, dass eine einzelne Goroutine viele Goroutinen gleichzeitig signalisieren kann. Signalisierung mit Daten ist immer ein 1 zu 1 Austausch zwischen Goroutinen.

Signalisierung mit Daten

Wenn Sie mit Daten signalisieren möchten, gibt es drei Kanalkonfigurationsoptionen, die Sie je nach Art der Garantie auswählen können.

Abbildung 3 : Signalisierung mit Daten

Die drei Kanaloptionen sind Ungepuffert, Gepuffert >1 oder Gepuffert =1.

  • Garantie

    • Ein ungepufferter Kanal gibt Ihnen die Garantie, dass ein gesendetes Signal empfangen wurde.
      • Weil der Empfang des Signals erfolgt, bevor das Senden des Signals abgeschlossen ist.
  • Keine Garantie

    • Ein gepufferter Kanal der Größe >1 gibt Ihnen keine Garantie, dass ein gesendetes Signal empfangen wurde.
      • Weil das Senden des Signals geschieht, bevor der Empfang des Signals abgeschlossen ist.
  • Verzögerte Garantie

    • Ein gepufferter Kanal der Größe =1 gibt Ihnen eine verzögerte Garantie. Es kann garantieren, dass das vorherige gesendete Signal empfangen wurde.
      • Da der Empfang des ersten Signals erfolgt, bevor das Senden des zweiten Signals abgeschlossen ist.

Die Größe des Puffers darf niemals eine Zufallszahl sein, sondern muss immer für eine genau definierte Einschränkung berechnet werden. Es gibt keine Unendlichkeit im Rechnen, alles muss eine genau definierte Einschränkung haben, egal ob es sich um Zeit oder Raum handelt.

Signalisierung ohne Daten

Signalisierung ohne Daten ist hauptsächlich der Stornierung vorbehalten. Es erlaubt einer Goroutine, einer anderen Goroutine zu signalisieren, dass sie das, was sie tun, abbrechen und weitermachen soll. Die Stornierung kann sowohl mit ungepufferten als auch mit gepufferten Kanälen implementiert werden, aber die Verwendung eines gepufferten Kanals, wenn keine Daten gesendet werden, ist ein Codegeruch.

Abbildung 4 : Signalisierung ohne Daten

Die eingebaute Funktion close wird verwendet, um ohne Daten zu signalisieren. Wie oben im Abschnitt Status erläutert, können Sie weiterhin Signale auf einem geschlossenen Kanal empfangen. Tatsächlich wird jeder Empfang auf einem geschlossenen Kanal nicht blockiert und die Empfangsoperation kehrt immer zurück.

In den meisten Fällen möchten Sie das Paket standard library context verwenden, um die Signalisierung ohne Daten zu implementieren. Das context -Paket verwendet einen ungepufferten Kanal darunter für die Signalisierung und die eingebaute Funktion close, um ohne Daten zu signalisieren.

Wenn Sie Ihren eigenen Kanal anstelle des Kontextpakets für die Stornierung verwenden, sollte Ihr Kanal vom Typ chan struct{} sein. Es ist die idiomatische Null-Raum-Methode, um einen Kanal anzuzeigen, der nur zur Signalisierung verwendet wird.

Szenarien

Mit diesen Attributen können Sie am besten verstehen, wie sie in der Praxis funktionieren, indem Sie eine Reihe von Codeszenarien durchlaufen. Ich denke gerne an Goroutinen als Menschen, wenn ich kanalbasierten Code lese und schreibe. Diese Visualisierung hilft wirklich, und ich werde es als Hilfe unten verwenden.

Signal mit ungepufferten Kanälen

Wenn Sie wissen müssen, dass ein gesendetes Signal empfangen wurde, kommen zwei Szenarien ins Spiel. Dies sind Warten auf Aufgabe und Warten auf Ergebnis.

Szenario 1 – Warten auf Aufgabe

Denken Sie darüber nach, Manager zu sein und einen neuen Mitarbeiter einzustellen. In diesem Szenario möchten Sie, dass Ihr neuer Mitarbeiter eine Aufgabe ausführt, aber er muss warten, bis Sie bereit sind. Dies liegt daran, dass Sie ihnen ein Stück Papier geben müssen, bevor sie beginnen.

Auflistung 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 }

In Zeile 02 in Listing 5 wird ein ungepufferter Kanal mit dem Attribut erstellt, dass string Daten mit dem Signal gesendet werden. Dann wird in der Leitung 04 ein Mitarbeiter eingestellt und aufgefordert, in der Leitung 05 auf Ihr Signal zu warten, bevor er seine Arbeit verrichtet. Zeile 05 ist der Kanal empfangen, wodurch der Mitarbeiter blockiert wird, während Sie auf das Stück Papier warten, das Sie senden. Sobald das Papier beim Mitarbeiter eingegangen ist, führt der Mitarbeiter die Arbeit aus und ist dann fertig und frei zu gehen.

Sie als Manager arbeiten gleichzeitig mit Ihrem neuen Mitarbeiter. Nachdem Sie den Mitarbeiter in Zeile 04 eingestellt haben, tun Sie (in Zeile 12), was Sie tun müssen, um den Mitarbeiter zu entsperren und zu signalisieren. Beachten Sie, dass nicht bekannt war, wie lange es dauern würde, dieses Stück Papier vorzubereiten, das Sie senden müssen.

Schließlich sind Sie bereit, den Mitarbeiter zu signalisieren. In Zeile 14 führen Sie ein Signal mit Daten aus, wobei die Daten das Stück Papier sind. Da ein ungepufferter Kanal verwendet wird, erhalten Sie eine Garantie, dass der Mitarbeiter das Papier erhalten hat, sobald Ihr Sendevorgang abgeschlossen ist. Der Empfang erfolgt vor dem Senden.

Technisch gesehen wissen Sie nur, dass der Mitarbeiter das Papier hat, wenn Ihr Kanalsendevorgang abgeschlossen ist. Nach beiden Kanaloperationen kann der Scheduler eine beliebige Anweisung ausführen. Die nächste Codezeile, die entweder von Ihnen oder dem Mitarbeiter ausgeführt wird, ist nicht deterministisch. Dies bedeutet, dass die Verwendung von print-Anweisungen Sie über die Reihenfolge der Dinge täuschen kann.

Szenario 2 – Warten auf Ergebnis

In diesem nächsten Szenario sind die Dinge umgekehrt. Dieses Mal möchten Sie, dass Ihr neuer Mitarbeiter eine Aufgabe sofort ausführt, wenn er eingestellt wird, und Sie müssen auf das Ergebnis seiner Arbeit warten. Sie müssen warten, weil Sie das Papier von ihnen benötigen, bevor Sie fortfahren können.

Auflistung 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 }

In Zeile 02 in Listing 6 wird ein ungepufferter Kanal mit dem Attribut erstellt, dass string Daten mit dem Signal gesendet werden. Dann wird in Zeile 04 ein Mitarbeiter eingestellt und sofort zur Arbeit gebracht. Nachdem Sie den Mitarbeiter in Zeile 04 eingestellt haben, warten Sie als nächstes in Zeile 12 auf den Papierbericht.

Sobald der Mitarbeiter die Arbeit in Zeile 05 abgeschlossen hat, sendet er das Ergebnis in Zeile 07 an Sie, indem er einen Kanal mit Daten sendet. Da es sich um einen ungepufferten Kanal handelt, erfolgt der Empfang vor dem Senden und der Mitarbeiter hat garantiert, dass Sie das Ergebnis erhalten haben. Sobald der Mitarbeiter diese Garantie hat, sind sie fertig und frei zu gehen. In diesem Szenario haben Sie keine Ahnung, wie lange der Mitarbeiter benötigt, um die Aufgabe zu beenden.

Kosten/Nutzen

Ein ungepufferter Kanal garantiert, dass ein gesendetes Signal empfangen wurde. Das ist toll, aber nichts ist kostenlos. Die Kosten für diese Garantie sind unbekannt Latenz. Im Szenario Warten auf Aufgabe hat der Mitarbeiter keine Ahnung, wie lange es dauern wird, bis Sie dieses Papier senden. Im Szenario Warten auf Ergebnis haben Sie keine Ahnung, wie lange der Mitarbeiter benötigen wird, um Ihnen dieses Ergebnis zu senden.

In beiden Szenarien müssen wir mit dieser unbekannten Latenz leben, da die Garantie erforderlich ist. Die Logik funktioniert nicht ohne dieses garantierte Verhalten.

Signal mit Daten – Keine Garantie – Gepufferte Kanäle >1

Wenn Sie nicht wissen müssen, dass ein gesendetes Signal empfangen wurde, kommen diese beiden Szenarien ins Spiel: Fan Out und Drop.

Ein gepufferter Kanal hat einen genau definierten Raum, in dem die gesendeten Daten gespeichert werden können. Wie entscheiden Sie, wie viel Platz Sie benötigen? Beantworten Sie diese Fragen:

  • Habe ich eine genau definierte Menge an Arbeit, die erledigt werden muss?
    • Wie viel Arbeit gibt es?
  • Wenn mein Mitarbeiter nicht mithalten kann, kann ich neue Arbeiten verwerfen?
    • Wie viel herausragende Arbeit belastet mich?
  • Welches Risiko bin ich bereit zu akzeptieren, wenn mein Programm unerwartet beendet wird?
    • Alles, was im Puffer wartet, geht verloren.

Wenn diese Fragen für das von Ihnen modellierte Verhalten keinen Sinn ergeben, ist es ein Codegeruch, dass die Verwendung eines gepufferten Kanals, der größer als 1 ist, wahrscheinlich falsch ist.

Szenario 1 – Fan Out

Mit einem Fan Out-Muster können Sie eine genau definierte Anzahl von Mitarbeitern auf ein Problem werfen, die gleichzeitig arbeiten. Da Sie für jede Aufgabe einen Mitarbeiter haben, wissen Sie genau, wie viele Berichte Sie erhalten. Sie können sicherstellen, dass in Ihrer Box der richtige Speicherplatz vorhanden ist, um alle diese Berichte zu erhalten. Dies hat den Vorteil, dass Ihre Mitarbeiter nicht warten müssen, bis Sie ihren Bericht einreichen. Sie müssen jedoch abwechselnd den Bericht in Ihre Box legen, wenn sie zur gleichen Zeit oder in der Nähe der Box ankommen.

Stellen Sie sich vor, Sie sind wieder der Manager, aber dieses Mal stellen Sie ein Team von Mitarbeitern ein. Sie haben eine individuelle Aufgabe, die jeder Mitarbeiter ausführen soll. Wenn jeder einzelne Mitarbeiter seine Aufgabe erledigt, muss er Ihnen einen Papierbericht zur Verfügung stellen, der in Ihrer Box auf Ihrem Schreibtisch abgelegt werden muss.

Auflistung 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 }

In Zeile 03 in Listing 7 wird ein gepufferter Kanal mit dem Attribut erstellt, dass string Daten mit dem Signal gesendet werden. Diesmal wird der Kanal dank der in Zeile 02 deklarierten Variablen emps mit 20 Puffern erstellt.

Zwischen den Zeilen 05 bis 10 werden 20 Mitarbeiter eingestellt, die sich sofort an die Arbeit machen. Sie haben keine Ahnung, wie lange jeder Mitarbeiter in Zeile 07 dauern wird. Dann senden die Mitarbeiter in Zeile 08 den Papierbericht, aber diesmal blockiert das Senden nicht das Warten auf einen Empfang. Da in der Box Platz für jeden Mitarbeiter vorhanden ist, konkurriert das Senden auf dem Kanal nur mit anderen Mitarbeitern, die ihren Bericht möglicherweise zur gleichen Zeit oder in der Nähe senden möchten.

Der Code zwischen den Zeilen 12 bis 16 ist alles, was Sie. Hier warten Sie, bis alle 20 Mitarbeiter ihre Arbeit beendet und ihren Bericht gesendet haben. In Zeile 12 befinden Sie sich in einer Schleife und in Zeile 13 sind Sie in einem Kanal blockiert, der auf Ihre Berichte wartet. Sobald ein Bericht empfangen wird, wird der Bericht in Zeile 14 gedruckt und die lokale Zählervariable wird dekrementiert, um anzuzeigen, dass ein Mitarbeiter fertig ist.

Szenario 2 – Drop

Ein Drop-Muster ermöglicht es Ihnen, Arbeit wegzuwerfen, wenn Ihre Mitarbeiter ausgelastet sind. Dies hat den Vorteil, dass Sie weiterhin Arbeit von Ihren Kunden akzeptieren und niemals Gegendruck oder Latenz bei der Annahme dieser Arbeit ausüben. Der Schlüssel hier ist zu wissen, wann Sie wirklich ausgelastet sind, damit Sie sich nicht zu wenig oder zu viel Arbeit leisten, die Sie zu erledigen versuchen. Normalerweise sind Integrationstests oder Metriken das, was Sie benötigen, um diese Zahl zu identifizieren.

Stellen Sie sich vor, Sie sind wieder der Manager und stellen einen einzelnen Mitarbeiter ein, um die Arbeit zu erledigen. Sie haben eine individuelle Aufgabe, die der Mitarbeiter ausführen soll. Wenn der Mitarbeiter seine Aufgabe beendet, ist es Ihnen egal, dass er fertig ist. Wichtig ist nur, ob Sie neue Arbeit in die Box legen können oder nicht. Wenn Sie den Versand nicht durchführen können, wissen Sie, dass Ihre Box voll ist und der Mitarbeiter ausgelastet ist. An diesem Punkt muss die neue Arbeit verworfen werden, damit die Dinge in Bewegung bleiben können.

Auflistung 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 }

In Zeile 03 in Listing 8 wird ein gepufferter Kanal mit dem Attribut erstellt, dass string Daten mit dem Signal gesendet werden. Diesmal wird der Kanal dank der in Zeile 02 deklarierten Konstante cap mit 5 Puffern erstellt.

Zwischen den Zeilen 05 bis 09 wird ein einzelner Mitarbeiter eingestellt, um die Arbeit zu erledigen. A for range wird für den Kanalempfang verwendet. Jedes Mal, wenn ein Blatt Papier empfangen wird, wird es in Zeile 07 verarbeitet.

Zwischen den Zeilen 11 bis 19 versuchen Sie, 20 Zettel an Ihren Mitarbeiter zu senden. Dieses Mal wird eine select -Anweisung verwendet, um den send innerhalb des ersten case in Zeile 14 auszuführen. Da die default -Klausel innerhalb von select in Zeile 16 verwendet wird und der send blockiert wird, weil im Puffer kein Platz mehr vorhanden ist, wird der send durch Ausführen von Zeile 17 abgebrochen.

Schließlich wird in Zeile 21 die eingebaute Funktion close gegen den Kanal aufgerufen. Dies signalisiert dem Mitarbeiter ohne Daten, dass er fertig und frei ist, sobald er seine zugewiesene Arbeit abgeschlossen hat..

Kosten/Nutzen

Ein gepufferter Kanal größer als 1 bietet keine Garantie dafür, dass ein gesendetes Signal jemals empfangen wird. Es gibt einen Vorteil, von dieser Garantie wegzugehen, nämlich die reduzierte oder keine Latenz in der Kommunikation zwischen zwei Goroutinen. Im Fan-Out-Szenario gibt es einen Pufferbereich für jeden Mitarbeiter, der einen Bericht sendet. Im Drop-Szenario wird der Puffer für die Kapazität gemessen, und wenn die Kapazität erreicht ist, wird die Arbeit gelöscht, damit die Dinge in Bewegung bleiben können.

In beiden Optionen müssen wir mit diesem Mangel an Garantie leben, da die Verringerung der Latenz wichtiger ist. Die Anforderung von Null bis minimaler Latenz stellt für die Gesamtlogik des Systems kein Problem dar.

Signal mit datenverzögertem und – gepuffertem Kanal 1

Wenn Sie wissen müssen, ob das zuvor gesendete Signal empfangen wurde, bevor Sie ein neues Signal senden, kommt das Szenario Warten auf Aufgaben ins Spiel.

Szenario 1 – Warten auf Aufgaben

In diesem Szenario haben Sie einen neuen Mitarbeiter, der jedoch mehr als nur eine Aufgabe ausführen wird. Sie werden sie viele Aufgaben, eine nach der anderen zu füttern. Sie müssen jedoch jede einzelne Aufgabe beenden, bevor sie eine neue beginnen können. Da sie jeweils nur an einer Aufgabe arbeiten können, kann es zu Latenzproblemen zwischen der Übergabe der Arbeit kommen. Wenn die Latenz reduziert werden könnte, ohne die Garantie zu verlieren, dass der Mitarbeiter an der nächsten Aufgabe arbeitet, könnte dies hilfreich sein.

Hier hat ein gepufferter Kanal von 1 Vorteile. Wenn zwischen Ihnen und dem Mitarbeiter alles im erwarteten Tempo läuft, muss keiner von Ihnen auf den anderen warten. Jedes Mal, wenn Sie ein Blatt Papier senden, ist der Puffer leer. Jedes Mal, wenn Ihr Mitarbeiter nach mehr Arbeit greift, ist der Puffer voll. Es ist eine perfekte Symmetrie des Arbeitsflusses.

Der beste Teil ist das. Wenn Sie zu irgendeinem Zeitpunkt versuchen, ein Blatt Papier zu senden, und dies nicht können, weil der Puffer voll ist, wissen Sie, dass Ihr Mitarbeiter ein Problem hat, und Sie hören auf. Hier kommt diese verzögerte Garantie ins Spiel. Wenn der Puffer leer ist und Sie den Versand durchführen, haben Sie die Garantie, dass Ihr Mitarbeiter die letzte von Ihnen gesendete Arbeit übernommen hat. Wenn Sie das Senden durchführen und Sie nicht können, haben Sie die Garantie, dass sie es nicht getan haben.

Listing 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 }

In Zeile 02 in Listing 9 wird ein gepufferter Kanal der Größe 1 mit dem Attribut erstellt, dass string Daten mit dem Signal gesendet werden. Zwischen den Zeilen 04 bis 08 wird ein einzelner Mitarbeiter eingestellt, um die Arbeit zu erledigen. A for range wird für den Kanalempfang verwendet. Jedes Mal, wenn ein Blatt Papier empfangen wird, wird es in Zeile 06 verarbeitet.

Zwischen den Zeilen 10 bis 13 beginnen Sie, Ihre Aufgaben an den Mitarbeiter zu senden. Wenn Ihr Mitarbeiter so schnell laufen kann, wie Sie senden können, verringert sich die Latenz zwischen Ihnen beiden. Aber mit jedem Senden, das Sie erfolgreich durchführen, haben Sie die Garantie, dass das letzte von Ihnen eingereichte Werk bearbeitet wird.

Schließlich wird in Zeile 15 die eingebaute Funktion close gegen den Kanal aufgerufen. Dies signalisiert dem Mitarbeiter ohne Daten, dass er fertig ist und frei gehen kann. Die letzte von Ihnen eingereichte Arbeit wird jedoch empfangen (geleert), bevor for range beendet wird.

Signal ohne Datenkontext

In diesem letzten Szenario sehen Sie, wie Sie eine laufende Goroutine mit einem Context Wert aus dem context Paket abbrechen können. Dies alles funktioniert durch die Nutzung eines ungepufferten Kanals, der geschlossen ist, um ein Signal ohne Daten auszuführen.

Sie sind ein letztes Mal der Manager und stellen einen einzelnen Mitarbeiter ein, um die Arbeit zu erledigen. Dieses Mal sind Sie nicht bereit, auf eine unbekannte Zeit zu warten, bis der Mitarbeiter fertig ist. Sie haben eine diskrete Frist und wenn der Mitarbeiter nicht rechtzeitig fertig wird, sind Sie nicht bereit zu warten.

Auflistung 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 }

In Zeile 02 in Listing 10 wird ein Dauerwert deklariert, der angibt, wie lange der Mitarbeiter die Aufgabe beenden muss. Dieser Wert wird in Zeile 04 verwendet, um einen context.Context -Wert mit einem Timeout von 50 Millisekunden zu erstellen. Die Funktion WithTimeout aus dem Paket context gibt einen Wert Context und eine Stornierungsfunktion zurück.

Das Paket context erstellt eine Goroutine, die den ungepufferten Kanal schließt, der dem Wert Context zugeordnet ist, sobald die Dauer erreicht ist. Sie sind dafür verantwortlich, die Funktion cancel aufzurufen, unabhängig davon, wie sich die Dinge entwickeln. Dadurch werden Dinge bereinigt, die für Context erstellt wurden. Es ist in Ordnung, wenn die Funktion cancel mehrmals aufgerufen wird.

In Zeile 05 wird die cancel -Funktion verschoben, um ausgeführt zu werden, sobald diese Funktion beendet ist. In Zeile 07 wird ein gepufferter Kanal von 1 erstellt, der vom Mitarbeiter verwendet wird, um Ihnen das Ergebnis seiner Arbeit zu senden. In den Zeilen 09 bis 12 wird der Mitarbeiter eingestellt und sofort zur Arbeit gebracht. Sie haben keine Ahnung, wie lange es dauern wird, bis der Mitarbeiter fertig ist.

Zwischen den Zeilen 14 bis 20 verwenden Sie die Anweisung select, um auf zwei Kanälen zu empfangen. Wenn Sie in Zeile 15 empfangen, warten Sie, bis der Mitarbeiter Ihnen sein Ergebnis sendet. Beim Empfang in Zeile 18 warten Sie ab, ob das Paket context signalisiert, dass die 50 Millisekunden abgelaufen sind. Welches Signal Sie zuerst erhalten, wird verarbeitet.

Ein wichtiger Aspekt dieses Algorithmus ist die Verwendung des gepufferten Kanals von 1. Wenn der Mitarbeiter nicht rechtzeitig fertig wird, fahren Sie fort, ohne den Mitarbeiter zu benachrichtigen. Aus Sicht der Mitarbeiter senden sie Ihnen den Bericht immer in Zeile 11 und sind blind, ob Sie dort sind oder nicht, um ihn zu erhalten. Wenn Sie einen ungepufferten Kanal verwenden, blockiert der Mitarbeiter für immer den Versuch, Ihnen den Bericht zu senden, wenn Sie fortfahren. Dies würde ein Goroutine-Leck verursachen. Daher wird ein gepufferter Kanal von 1 verwendet, um dies zu verhindern.

Fazit

Die Attribute der Signalisierung um Garantien, Kanalstatus und Senden sind wichtig zu wissen und zu verstehen, wenn Kanäle (oder Parallelität) verwendet werden. Sie helfen Ihnen bei der Implementierung des besten Verhaltens, das Sie für die gleichzeitigen Programme und Algorithmen benötigen, die Sie schreiben. Sie helfen Ihnen, Fehler zu finden und potenziell schlechten Code auszuspähen.

In diesem Beitrag habe ich einige Beispielprogramme geteilt, die zeigen, wie die Attribute der Signalisierung in verschiedenen Szenarien funktionieren. Es gibt Ausnahmen von jeder Regel, aber diese Muster sind eine gute Grundlage, um zu beginnen.

Überprüfen Sie diese Umrisse als Zusammenfassung, wann und wie Sie effektiv über Kanäle nachdenken und diese nutzen können:

Sprachmechanik

  • Verwenden Sie Kanäle, um Goroutinen zu orchestrieren und zu koordinieren.
    • Konzentrieren Sie sich auf die Signalisierungsattribute und nicht auf das Teilen von Daten.
    • Signalisierung mit Daten oder ohne Daten.
    • Stellen Sie ihre Verwendung zum Synchronisieren des Zugriffs auf den freigegebenen Status in Frage.
      • Es gibt Fälle, in denen Kanäle dafür einfacher sein können, aber zunächst in Frage gestellt werden.
  • Ungepufferte Kanäle:
    • Der Empfang erfolgt vor dem Senden.
    • Nutzen: 100% garantieren die signal empfangen wurde.
    • Kosten: Unbekannte Latenzzeit, wann das Signal empfangen wird.
  • Gepufferte Kanäle:
    • Senden geschieht vor dem Empfang.
    • Vorteil: Reduzieren Sie die Blockierungslatenz zwischen den Signalisierungen.
    • Kosten: Keine Garantie, wenn das Signal empfangen wurde.
      • Je größer der Puffer, desto weniger Garantie.
      • Puffer von 1 können geben sie eine verzögert senden von garantie.
  • Kanäle schließen:
    • Schließen geschieht vor dem Empfang (wie gepuffert).
    • Signalisierung ohne Daten.
    • Perfekt zum Signalisieren von Stornierungen und Fristen.
  • nil kanäle:
    • Senden und Empfangen block.
    • Schalten Sie die Signalisierung aus
    • Perfekt für Ratenbegrenzung oder kurzfristige Unterbrechungen.

Designphilosophie

  • Wenn ein bestimmtes Senden auf einem Kanal dazu führen kann, dass die sendende Goroutine blockiert wird:
    • Darf keinen gepufferten Kanal größer als 1 verwenden.
      • Puffer größer als 1 müssen Grund/Maße haben.
    • Muss wissen, was passiert, wenn die sendende Goroutine blockiert.
  • Wenn ein bestimmter Send auf einem Kanal NICHT dazu führt, dass die sendende Goroutine blockiert:
    • Sie haben die genaue Anzahl der Puffer für jeden send .
      • Fächermuster
    • Sie haben den Puffer für die maximale Kapazität gemessen.
      • Tropfenmuster
  • Weniger ist mehr mit Puffern.
    • Denken Sie nicht an die Leistung, wenn Sie über Puffer nachdenken.
    • Puffer können helfen, die Blockierungslatenz zwischen Signalisierung zu reduzieren.
      • Die Reduzierung der Blockierungslatenz auf Null bedeutet nicht unbedingt einen besseren Durchsatz.
      • Wenn ein Puffer von einem Ihnen einen ausreichenden Durchsatz bietet, behalten Sie ihn bei.
      • Frage Puffer, die größer als eins sind und nach Größe messen.
      • Finden Sie den kleinstmöglichen Puffer, der einen ausreichenden Durchsatz bietet.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.