Erklären: Kommunizieren Sie nicht, indem Sie Speicher teilen; Speicher teilen durch Kommunikation

8

Ich frage mich, was ist die bodenständigste Erklärung dieses berühmten Zitats:

  

Kommunizieren Sie nicht, indem Sie Speicher teilen; Speicher teilen durch Kommunikation. (R. Pike)

Im Go-Speicher-Modell kann ich Folgendes lesen:

  

Ein Send auf einem Kanal passiert, bevor der entsprechende Empfang von diesem Kanal abgeschlossen ist. (Golang Spec)

Es gibt auch einen speziellen golang-Artikel , in dem das Zitat erklärt wird. Und der Schlüsselbeitrag ist ein funktionierendes Beispiel von Andrew G.

Nun. Manchmal zu viel reden .... Ich habe aus dem Memory Spec-Zitat abgeleitet und auch anhand des Arbeitsbeispiels dieses:

  

Nachdem eine goroutine1 (irgendwas) über einen Kanal an eine goroutine2 gesendet hat, müssen alle Änderungen (überall im Speicher), die von goroutine1 ausgeführt werden, für goroutine2 sichtbar sein, nachdem sie über denselben Kanal empfangen wurden. (Golang Lemma von mir:)

Daher leite ich die bodenständige Erklärung des berühmten Zitats ab:

  

Um den Speicherzugriff zwischen zwei Prozessoren zu synchronisieren, müssen Sie diesen Speicher nicht über den Kanal senden. Gut genug ist es, vom Kanal zu empfangen (auch gar nichts). Sie werden alle Änderungen sehen, die während des Sendens (überall) durch das Senden der Goroutine (an den Kanal) geschrieben wurden. (Natürlich vorausgesetzt, dass keine anderen Goroutines in dieselbe Erinnerung schreiben.) Update (2) 8-26-2017

Ich habe eigentlich zwei Fragen:

1) Ist meine Schlussfolgerung richtig?

2) Hilft meine Erklärung?

Aktualisieren (1) Ich nehme ungepufferte Kanäle an . Beschränken wir uns zunächst darauf, dass wir uns nicht mit zu vielen Unbekannten überarbeiten.

Bitte konzentrieren wir uns auch auf eine einfache Verwendung von zwei Göroutinen, die über einen einzelnen Kanal und damit verbundene Memory-Effekte kommunizieren, und nicht auf Best Practices - das geht über den Rahmen dieser Frage hinaus.

Um den Umfang meiner Frage besser zu verstehen, gehe man davon aus, dass die Goroutinen auf jede Art von Speicherstruktur zugreifen können - nicht nur auf primitive - und es kann eine große sein, es kann eine Zeichenkette, eine Karte, ein Array oder was auch immer sein.

    
honzajde 03.04.2016, 21:28
quelle

6 Antworten

2

Im Wesentlichen ja. Alle Werte, die Variablen vor dem Senden des Kanals zugewiesen wurden, können nach dem Lesen des Kanals beobachtet werden, da die Kanaloperation eine Ordnungsbeschränkung auferlegt. Aber es ist wichtig, sich an den anderen Teil der Gleichung zu erinnern: Wenn Sie garantieren möchten, dass diese Werte eingehalten werden, müssen Sie sicherstellen, dass niemand sonst zwischen Variablen schreiben kann lesen. Offensichtlich wäre die Verwendung von Sperren möglich, aber sinnlos zugleich, denn wenn Sie bereits Sperren und Cross-Thread-Speichermodifikationen kombinieren, welchen Nutzen haben Sie dann von den Kanälen? Sie könnten etwas so Einfaches wie einen Boolean weitergeben, als ein Token, das exklusiven Zugriff auf die globalen Daten erlaubt, und es wäre zu 100% korrekt hinsichtlich der Speichermodellgarantien (solange Ihr Code keine Fehler hatte), < Es würde wahrscheinlich nur ein schlechtes Design sein, weil du die Dinge ohne einen guten Grund implizit machen und eine Handlung in einer Entfernung machen würdest; Die explizite Weitergabe der Daten wird in der Regel klarer und weniger fehleranfällig sein.

    
hobbs 03.04.2016, 22:34
quelle
10

Dieses berühmte Zitat kann ein wenig verwirrend sein, wenn es auch zu wörtlich genommen wird. Lassen Sie uns auf die grundlegenden Komponenten eingehen und sie richtig definieren:

%Vor%
  1. Dies bedeutet, dass verschiedene Ausführungsthreads über die Änderung des Status anderer Threads informiert werden, indem Speicher gelesen wird, der an anderer Stelle geändert wird. Ein perfektes Beispiel dafür (obwohl für Prozesse anstelle von Threads) ist die POSIX Shared Memory API: Ссылка . Diese Technik benötigt eine korrekte Synchronisation, da Datenrennen ziemlich einfach auftreten können.
  2. Hier bedeutet das, dass es tatsächlich einen Teil des Speichers gibt, physisch oder virtuell, der aus mehreren Threads modifiziert werden kann und auch von diesen liest. Es gibt keine explizite Vorstellung von Besitz, der Speicherplatz ist von allen Threads gleichermaßen zugänglich.
  3. Das ist ganz anders. In Go ist das Teilen von Speicher wie oben möglich, und Datenrennen können ziemlich einfach auftreten. Was das bedeutet, ist, eine Variable in einer Goroutine zu ändern, sei es ein einfacher Wert wie ein int oder eine komplizierte Datenstruktur wie a map, und verschenken Besitz, indem Sie den Wert oder einen Zeiger auf den Wert an eine andere goroutine über den Kanalmechanismus senden. Im Idealfall gibt es keinen gemeinsamen Raum, jede Goroutine sieht nur den Teil des Speichers, den sie besitzt.
  4. Kommunikation bedeutet hier einfach, dass ein Kanal, der nur eine Warteschlange ist, einer Goroutine erlaubt, von ihm zu lesen und so über den Besitz eines neuen Teils des Speichers informiert zu werden, während ein anderer ihn sendet und akzeptiert, Besitz zu verlieren. Es ist ein einfaches Nachrichtenübergabemuster.

Zusammenfassend kann man sagen, was das Zitat bedeutet:

  

Übertreiben Sie die Inter-Thread-Kommunikation nicht durch die Verwendung von gemeinsam genutztem Speicher und komplizierten, fehleranfälligen Synchronisations-Primitiven, sondern verwenden Sie Message-Passing zwischen den Gruoutinen (grüne Threads), so dass Variablen und Daten nacheinander verwendet werden können. p>

Die Verwendung der Wortfolge ist hier bemerkenswert, weil sie die Philosophie beschreibt, die das Konzept der Göroutinen und Kanäle inspirierte: Sequentielle Prozesse kommunizieren .

    
SirDarius 03.04.2016 22:12
quelle
3

Ich glaube nicht. Statt nur eine feste Speicheradresse mit einer Sperre oder anderen gleichzeitigen Primitiven zu schützen, können Sie das Programm so gestalten, dass nur ein Ausführungsstrom auf diesen Speicher zugreifen darf by design .

Der einfachste Weg, dies zu erreichen, besteht darin, den Verweis auf den Speicher nach Kanälen zu teilen. Sobald Sie die Referenz über den Kanal gesendet haben, vergessen Sie . Auf diese Weise hat nur die Routine Zugriff auf diesen Kanal.

    
fabrizioM 03.04.2016 21:33
quelle
3

Es gibt zwei Sätze darin; Für ein besseres Verständnis müssen diese zuerst getrennt betrachtet und dann zusammengeschlagen werden. Don't communicate by sharing memory; bedeutet, dass verschiedene Threads nicht miteinander kommunizieren sollten, indem sie die stringenten und fehleranfälligen Speichersichtbarkeits- und Synchronisierungsrichtlinien wie Speicherbarriere usw. einhalten. (Beachten Sie, dass dies möglich ist, aber bald komplex und sehr fehlerhaft werden kann) Datenrennen). Vermeiden Sie daher die Einhaltung manueller, programmatischer Sichtbarkeitskonstrukte, die meist durch geeignete Synchronisationen in Programmiersprachen wie Java erreicht werden.

share memory by communicating. bedeutet, wenn ein Thread irgendwelche Änderungen (Schreibvorgänge) in einem Speicherbereich vorgenommen hat, sollte er denselben (den Speicherbereich) dem Thread mitteilen, der an demselben Speicherbereich interessiert ist; Beachten Sie, dass dies den Speicherbereich bereits auf nur zwei Threads beschränkt hat.

Lies nun die obigen zwei Absätze in Verbindung mit dem Golang-Speichermodell, das sagt: A send on a channel happens before the corresponding receive from that channel completes. Die Beziehung happes-before bestätigt, dass das Schreiben durch die erste Goroutine für die zweite Goroutine sichtbar ist, die die Speicherreferenz am anderen Ende empfängt des Kanals!

    
nawazish-stackoverflow 05.10.2016 17:41
quelle
3

Lass mich einfach und auf den Punkt hier gehen.

  

Kommunizieren Sie nicht, indem Sie Speicher freigeben.

Es ist so, als würden Sie zum Beispiel mit Threads kommunizieren. Sie müssen Variablen oder Mutexe verwenden, um den Speicher zu sperren, damit niemand darauf lesen und schreiben kann, bis die Kommunikation abgeschlossen ist.

  

Teilen Sie Speicher durch Kommunikation

In Go-Routinen bewegen sich die Werte auf Kanälen, anstatt den Speicher zu blockieren. Der Sender benachrichtigt den Empfänger, von diesem Kanal zu empfangen und teilt daher den Speicher, indem er mit dem Empfänger kommuniziert, um von einem Kanal zu kommen.

    
Himanshu 11.06.2017 13:50
quelle
1
  

1) Ist meine Schlussfolgerung richtig?

Ich denke schon, wenn es bedeutet, was ich hoffe. Der Grund für die Sprache in den Spezifikationen unter Verwendung der "zufälligen" Terminologie ist, dass sie eine gut definierte Form der Kommunikation zum Ausdrücken von Ideen bereitstellt.

Das Problem mit Ihrer Beschreibung ist, dass Sie Ihre Zufallsreihenfolge nicht explizit definiert haben. Ich denke jedoch, dass Sie eine Anweisung implizieren. Wenn Sie meinen, dass "Operationen in goroutine a, die vor goroutine a an einem bestimmten Punkt der Synchronisierung geschehen, von goroutine b sichtbar sind, nachdem goroutine b diesen gleichen Punkt der Synchronisation auch beobachtet hat" - sogar hier, der "Punkt der Synchronisation" ist schlecht definiert - obwohl ich erwarte, dass Sie es verstehen. Solch ein Punkt kann im Zufall definiert werden, wie es die Spezifikation tut.

  

2) Hilft meine Erklärung?

Vielleicht können Menschen, die mit dem Thema nicht vertraut sind oder Schwierigkeiten haben, den Zufallsstil der Beschreibung zu verstehen, Ihre Beschreibung leichter interpretieren. Es gibt jedoch Einschränkungen und mögliche praktische Probleme in der Anwendung Ihrer Beschreibung, wie folgt:

  • Sie haben nicht genau definiert, dass das "Senden", von dem Sie sprechen, ein Synchronisationspunkt ist. Wenn Sie einen Send auf einem ungepufferten Kanal meinen, dann wird ja ein gemeinsamer Synchronisationspunkt erstellt, der durch die Spezifikation eine strikte Zufallsreihenfolge einführt.
  • Wenn Sie davon ausgehen, dass das oben genannte Ergebnis wahr ist, haben Sie einen Synchronisationspunkt beschrieben, der nur eine Seite des Punktes des ursprünglichen Hinweises betrifft. Der ursprüngliche Ratschlag umfasst das Konzept der "Eigentumsübertragung", und das hat weniger damit zu tun, Synchronisationspunkte oder Zufall zu schaffen, als vielmehr mit der längerfristigen Pflege von Code zu tun, der auf potenziell gemeinsam genutztem Speicher beruht. Das Konzept besteht darin, dass anstatt den Zugriff auf ein Speichersegment an zwei Stellen zu behalten und separate gemeinsame Synchronisationspunkte (wie Mutexe) zu erstellen, stattdessen der einzige Verweis auf das Objekt von einem Besitzer zum anderen übergeben werden kann. Entwerfen von Software auf diese Weise verhindert die versehentliche Änderung außerhalb von Synchronisationspunkten, die oft in Software mit granularen Verwendung von Mutexes und umfangreiche Nutzung von Shared Memory beobachtet wurde.

Mutexe oder "explizite Synchronisationspunkte" sind genau das Gegenteil des Hinweises - sie sind ein geteiltes Stück Speicher, das verwendet wird, um einen Synchronisationspunkt "kommunizieren durch gemeinsame Nutzung von Speicher" zu kommunizieren, obwohl sie Mutexe tief unter dem haben hood, channels sind ein abstrakter Mechanismus, um den Besitz eines Objekts (den gesendeten Wert) von einer Goroutine (Absender) an eine andere Goroutine (Empfänger) zu übergeben. Der Punkt dort ist, ignoriert, wie Kanäle implementiert werden, dass der Benutzer Speicher (den Wert) geteilt hat, indem er es von einem Besitzer (Goroutine a) zu einer anderen (Goroutine b) kommuniziert. Wenn Sie einen Kanal verwenden, um nicht verwandte Daten zu senden, um einen Synchronisationspunkt zu erstellen, verwenden Sie ihn im Wesentlichen als Mutex, und dies ist näher an der Kommunikation durch die gemeinsame Nutzung von Speicher (Fokus auf den Kanal) und nicht durch Kommunikation (Fokus auf der Wert).

Ich hoffe, das hilft.

    
raggi 03.04.2016 23:15
quelle

Tags und Links