web-dev-qa-db-ja.com

説明:メモリを共有してコミュニケーションをとらないでください。通信によるメモリの共有

この有名な引用の最も現実的な説明は何でしょうか。

メモリを共有して通信しないでください。通信によってメモリを共有します。 (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のものだけでなく-それは大きなものであることができ、それは文字列、マップ、配列などであることができます。

22
honzajde

本質的には、そうです。チャネル操作は順序の制約を課すため、チャネル送信の前に変数に割り当てられた値は、チャネルの読み取り後に監視する資格があります。ただし、方程式の他の部分を覚えておくことが重要です。これらの値が守られていることを保証したい場合は、書き込みと書き込みの間に他の誰もこれらの変数に書き込みできないようにする必要があります。読んだ。明らかにロックを使用することは可能ですが、同時に無意味です。すでにロックとクロススレッドメモリの変更を組み合わせている場合、チャネルからどのようなメリットが得られるのでしょうか。ブール値のような単純なもの、グローバルデータへの排他的アクセスを許可するトークンとして渡すことができ、メモリモデルの保証に関しては100%正確です(コードにバグがない限り)- おそらく悪い設計になるでしょう正当な理由なしにすべてを暗黙的かつ遠隔作用にするためです。通常、データを明示的に渡すと、より明確になり、エラーが発生しにくくなります。

6
hobbs

この有名な引用は、あまりにも散らかっていると少し混乱する可能性があります。それをより基本的なコンポーネントに分解し、それらを適切に定義しましょう。

Don't communicate by sharing memory; share memory by communicating
      ---- 1 ----    ------ 2 -----  ---- 3 -----    ----- 4 -----
  1. これは、別の場所で変更されるメモリを読み取ることにより、実行のさまざまなスレッドに他のスレッドの状態の変化が通知されることを意味します。これの完璧な例(スレッドではなくプロセスの場合)は、POSIX共有メモリAPIです: http://man7.org/linux/man-pages/man7/shm_overview.7.html 。データの競合は非常に簡単に発生する可能性があるため、この手法には適切な同期が必要です。
  2. ここで、これは、物理または仮想のメモリの一部が実際に存在することを意味します。これらのスレッドは、複数のスレッドから変更でき、それらからも読み取ることができます。所有権の明示的な概念はありません。メモリ空間はすべてのスレッドから等しくアクセスできます。
  3. これはまったく異なります。 Goでは、上記のようにメモリを共有することが可能であり、データ競合が非常に簡単に発生する可能性があるため、実際には、intのような単純な値であれ、複雑なデータであれ、ゴルーチンの変数を変更することを意味しますマップのような構造であり、チャネルメカニズムを介して値または値へのポインタを別のゴルーチンに送信することにより、所有権を譲渡します。したがって、理想的には、共有スペースはなく、各ゴルーチンは所有するメモリの部分のみを認識します。
  4. ここでの通信とは、単にキューであるチャネルが1つのgoroutineからチャネルを読み取ってメモリの新しい部分の所有権を通知できるようにする一方で、別のチャネルが送信して所有権を失うことを受け入れることを意味します。シンプルなメッセージパッシングパターンです。

結論として、引用の意味は次のように要約できます。

共有メモリと複雑でエラーが発生しやすい同期プリミティブを使用してスレッド間通信を過剰設計しないでください。代わりに、ゴルーチン(緑のスレッド)間のメッセージ受け渡しを使用して、変数とデータをそれらの間で順番に使用できるようにします。

ここでのWordシーケンスの使用は、ゴルーチンとチャネルの概念に影響を与えた哲学を説明しているため、注目に値します。 Communicating Sequential Processes

19
SirDarius

私はそうは思いません。要旨は、1つの固定メモリアドレスをロックまたは他の並行プリミティブで保護する代わりに、1つの実行ストリームのみがこのメモリにアクセスできるようにプログラムを設計できます設計による

これを実現する簡単な方法は、メモリへの参照をチャネルごとに共有することです。チャネルを介して参照を送信すると、忘れてください。このようにして、そのチャネルを消費するルーチンのみがそのチャネルにアクセスできます。

5
fabrizioM

これには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ルーチンでは、値はメモリをブロックするのではなくチャネル上を移動し、送信者は受信者にそのチャネルから受信するように通知します。したがって、受信者と通信してチャネルから取得することにより、メモリを共有します。

4
Himanshu

1)私の結論は正しいですか?

それが私が望むことを意味するのであれば、そう思います。 「ハッペンスタンス」という用語を使用した仕様で言語が使用されている理由は、アイデアを表現するための明確なコミュニケーション形式を提供するためです。

説明の問題は、実際に発生順序を明示的に定義していないことです。あなたは命令を暗示していると思いますが。 「ゴルーチンaが特定の同期ポイントで動作する前に発生するゴルーチンaの操作は、ゴルーチンbも同じ同期ポイントを確認した後、ゴルーチンbによって表示されます」-ここでも「同期ポイント」定義が不十分です-あなたがそれを理解していることを期待していますが。そのような点は、仕様がそうであるように、偶然に定義することができます。

2)私の説明は役に立ちますか?

たぶん、トピックに不慣れな人や、説明の偶然のスタイルを理解するのに苦労している人は、あなたの説明を解釈しやすくするかもしれません。ただし、次のように、アプリケーションの説明には制限と潜在的な実際的な問題があります。

  • あなたが話している「送信」が同期ポイントであることを厳密に定義していません。バッファリングされていないチャネルでの送信を意味する場合は、そうです。これにより、共有同期ポイントが作成され、仕様により、厳密な偶然の順序が導入されます。
  • 上記が当てはまると仮定して、同期ポイントについて説明しましたが、これは元のアドバイスのポイントの片側のみを扱います。元のアドバイスには、「所有権の譲渡」の概念が含まれており、同期ポイントや偶然の作成にはあまり関係がなく、共有メモリに依存する可能性のあるコードの長期保守に関係しています。概念は、メモリの一部へのアクセスを2つの場所に保持し、個別の共有同期ポイント(ミューテックスなど)を作成する代わりに、オブジェクトへの参照を1つの所有者から別の所有者に渡すことができるというものです。このようにソフトウェアを設計することで、同期ポイントの外部での偶発的な変更を防ぐことができます。これは、ミューテックスをきめ細かく使用し、共有メモリを多用するソフトウェアでよく見られます。

ミューテックスまたは「明示的な同期ポイント」は、アドバイスの正反対です。これらは、「メモリを共有して通信する」同期ポイントを通信するために使用される共有メモリですが、内部にミューテックスがある場合でも、チャネルはは、オブジェクト(送信された値)の所有権を1つのゴルーチン(送信者)から別のゴルーチン(受信者)に渡すための抽象的なメカニズムです。チャネルの実装方法を無視すると、ユーザーはメモリ(値)をある所有者(ゴルーチンa)から別の所有者(ゴルーチンb)に通信することで共有しているという点があります。チャネルを使用して無関係なデータを送信して同期ポイントを作成する場合、基本的にそれをミューテックスとして使用します。これは、通信による共有(チャネルに焦点を合わせる)ではなく、メモリの共有(チャネルに焦点を合わせる)による通信に近いです。値)。

これがお役に立てば幸いです。

2
raggi