この有名な引用の最も現実的な説明は何でしょうか。
メモリを共有して通信しないでください。通信によってメモリを共有します。 (R.パイク)
Goメモリモデル 私はこれを読むことができます:
チャネルでの送信は、そのチャネルからの対応する受信が完了する前に行われます。 (Golang仕様)
専用の golangの記事 引用を説明するものもあります。そして、重要な貢献は、Andrew Gによる 実用的な例 です。
上手。時々、あまりにも多くのことを話します....私はメモリスペックの引用から、そしてこれを実際の例を見ることによってこれを導きました:
Goroutine1がチャネルを介して(何でも)goroutine2に送信した後、goroutine1によって行われたすべての変更(メモリ内の任意の場所)は、同じチャネルを介して受信した後、goroutine2に表示される必要があります。 (Golang Lemma by Me :)
したがって、私は有名な引用の「地球への説明」を導き出します。
2つのゴルーチン間でメモリアクセスを同期するために、チャネル経由でそのメモリを送信する必要はありません。チャネルから受信するだけで十分です(何もない場合でも)。送信時に(チャネルへの)ゴルーチン送信によって(どこでも)書き込まれた変更が表示されます。 (もちろん、他のゴルーチンが同じメモリに書き込んでいないと仮定します。)更新(2)8-26-2017
私は実際に2つの質問があります:
1)私の結論は正しいですか?
2)私の説明は役に立ちますか?
更新(1)私はバッファリングされていないチャネルを想定しています。あまりにも多くの未知数で自分自身をオーバーホールすることを避けるために、最初に自分自身をそれに制限しましょう。
また、ベストプラクティスではなく、単一のチャネルと関連するメモリ効果を介して通信する2つのゴルーチンの単純な使用例に焦点を当てましょう。これは、この質問の範囲を超えています。
私の質問の範囲をよりよく理解するために、goroutineが任意の種類のメモリ構造にアクセスできると仮定します-それはprimitveのものだけでなく-それは大きなものであることができ、それは文字列、マップ、配列などであることができます。
本質的には、そうです。チャネル操作は順序の制約を課すため、チャネル送信の前に変数に割り当てられた値は、チャネルの読み取り後に監視する資格があります。ただし、方程式の他の部分を覚えておくことが重要です。これらの値が守られていることを保証したい場合は、書き込みと書き込みの間に他の誰もこれらの変数に書き込みできないようにする必要があります。読んだ。明らかにロックを使用することは可能ですが、同時に無意味です。すでにロックとクロススレッドメモリの変更を組み合わせている場合、チャネルからどのようなメリットが得られるのでしょうか。ブール値のような単純なもの、グローバルデータへの排他的アクセスを許可するトークンとして渡すことができ、メモリモデルの保証に関しては100%正確です(コードにバグがない限り)- おそらく悪い設計になるでしょう正当な理由なしにすべてを暗黙的かつ遠隔作用にするためです。通常、データを明示的に渡すと、より明確になり、エラーが発生しにくくなります。
この有名な引用は、あまりにも散らかっていると少し混乱する可能性があります。それをより基本的なコンポーネントに分解し、それらを適切に定義しましょう。
Don't communicate by sharing memory; share memory by communicating
---- 1 ---- ------ 2 ----- ---- 3 ----- ----- 4 -----
int
のような単純な値であれ、複雑なデータであれ、ゴルーチンの変数を変更することを意味しますマップのような構造であり、チャネルメカニズムを介して値または値へのポインタを別のゴルーチンに送信することにより、所有権を譲渡します。したがって、理想的には、共有スペースはなく、各ゴルーチンは所有するメモリの部分のみを認識します。結論として、引用の意味は次のように要約できます。
共有メモリと複雑でエラーが発生しやすい同期プリミティブを使用してスレッド間通信を過剰設計しないでください。代わりに、ゴルーチン(緑のスレッド)間のメッセージ受け渡しを使用して、変数とデータをそれらの間で順番に使用できるようにします。
ここでのWordシーケンスの使用は、ゴルーチンとチャネルの概念に影響を与えた哲学を説明しているため、注目に値します。 Communicating Sequential Processes 。
私はそうは思いません。要旨は、1つの固定メモリアドレスをロックまたは他の並行プリミティブで保護する代わりに、1つの実行ストリームのみがこのメモリにアクセスできるようにプログラムを設計できます設計による。
これを実現する簡単な方法は、メモリへの参照をチャネルごとに共有することです。チャネルを介して参照を送信すると、忘れてください。このようにして、そのチャネルを消費するルーチンのみがそのチャネルにアクセスできます。
これには2つの文があります。より完全に理解するには、これらを最初に個別に見てから一緒に結合する必要があります。したがって、Don't communicate by sharing memory;
は、メモリバリアなどの厳格でエラーが発生しやすいメモリの可視性および同期ポリシーに準拠することにより、異なるスレッドが互いに通信してはならないことを意味します。(実行できることに注意してください。ただし、データの競合により、すぐに複雑になり、非常にバグが発生する可能性があります)。したがって、Javaなどのプログラミング言語での適切な同期によって主に達成される手動のプログラムによる可視性構造に準拠することは避けてください。
share memory by communicating.
は、1つのスレッドがメモリ領域に変更(書き込み)を行った場合、同じ(メモリ領域)を同じメモリ領域に関係するスレッドに通信する必要があることを意味します。これにより、メモリのスコープが2つのスレッドのみに制限されていることに注意してください。
次に、上記の2つのパラグラフをA send on a channel happens before the corresponding receive from that channel completes.
というgolangメモリモデルと併せて読みます。先に発生する関係により、最初のgoroutineによる書き込みが、2番目のgoroutineが他の端でメモリ参照を受信できるようになります。チャンネル!
簡単に、ここで要点を述べましょう。
メモリを共有して通信しないでください。
たとえば、スレッドを使用して通信しているときは、変数またはミューテックスを使用してメモリをロックし、通信が完了するまで誰かがメモリを読み書きできないようにする必要があります。
通信によるメモリの共有
Goルーチンでは、値はメモリをブロックするのではなくチャネル上を移動し、送信者は受信者にそのチャネルから受信するように通知します。したがって、受信者と通信してチャネルから取得することにより、メモリを共有します。
1)私の結論は正しいですか?
それが私が望むことを意味するのであれば、そう思います。 「ハッペンスタンス」という用語を使用した仕様で言語が使用されている理由は、アイデアを表現するための明確なコミュニケーション形式を提供するためです。
説明の問題は、実際に発生順序を明示的に定義していないことです。あなたは命令を暗示していると思いますが。 「ゴルーチンaが特定の同期ポイントで動作する前に発生するゴルーチンaの操作は、ゴルーチンbも同じ同期ポイントを確認した後、ゴルーチンbによって表示されます」-ここでも「同期ポイント」定義が不十分です-あなたがそれを理解していることを期待していますが。そのような点は、仕様がそうであるように、偶然に定義することができます。
2)私の説明は役に立ちますか?
たぶん、トピックに不慣れな人や、説明の偶然のスタイルを理解するのに苦労している人は、あなたの説明を解釈しやすくするかもしれません。ただし、次のように、アプリケーションの説明には制限と潜在的な実際的な問題があります。
ミューテックスまたは「明示的な同期ポイント」は、アドバイスの正反対です。これらは、「メモリを共有して通信する」同期ポイントを通信するために使用される共有メモリですが、内部にミューテックスがある場合でも、チャネルはは、オブジェクト(送信された値)の所有権を1つのゴルーチン(送信者)から別のゴルーチン(受信者)に渡すための抽象的なメカニズムです。チャネルの実装方法を無視すると、ユーザーはメモリ(値)をある所有者(ゴルーチンa)から別の所有者(ゴルーチンb)に通信することで共有しているという点があります。チャネルを使用して無関係なデータを送信して同期ポイントを作成する場合、基本的にそれをミューテックスとして使用します。これは、通信による共有(チャネルに焦点を合わせる)ではなく、メモリの共有(チャネルに焦点を合わせる)による通信に近いです。値)。
これがお役に立てば幸いです。