Clojureの可変ストレージタイプ
-
06-07-2019 - |
質問
このサイトで入手可能なAPIとドキュメントからClojureを学ぼうとしています。 Clojureの可変ストレージについては少しわかりません。理解が正しいことを確認したいと思います。間違っているアイデアがあれば教えてください。
編集:正確性に関するコメントを受け取ったため、これを更新しています。
免責事項:これらの情報はすべて非公式であり、誤っている可能性があります。 Clojureの仕組みを理解するためにこの投稿を使用しないでください。
Vars には常にルートバインディングと、場合によってはスレッドごとのバインディングが含まれます。これらは命令型言語の通常の変数に匹敵し、スレッド間で情報を共有するのには適していません。 (アーサーウルフェルトに感謝)
参照は、単一のトランザクションで任意の数の参照の状態を変更できるアトミックトランザクションをサポートするスレッド間で共有される場所です。トランザクションは同期式の終了時にコミットされ(dosync)、競合はSTMマジック(ロールバック、キュー、待機など)で自動的に解決されます
エージェントは、独立したアクション関数をディスパッチしてエージェントの状態を変更することにより、最小限のオーバーヘッドでスレッド間で情報を非同期的に共有できる場所です。エージェントはすぐに返されるため、ブロックされませんが、ディスパッチされた機能が完了するまでエージェントの値は設定されません。
Atoms は、スレッド間で同期的に共有できる場所です。異なるスレッド間の安全な操作をサポートしています。
これらの構造をいつ使用するかに基づいた、わかりやすい要約を次に示します。
- 変数は、命令型言語の通常の古い変数のようなものです。 (可能な場合は避けてください)
- AtomsはVarsに似ていますが、スレッド共有の安全性により、すぐに読み取りと安全な設定が可能です。 (Martinに感謝)
- エージェントはAtomに似ていますが、エージェントをブロックするのではなく、値を計算するために新しいスレッドを生成し、値を変更している場合にのみブロックし、割り当てが完了したことを他のスレッドに知らせることができます。
- Refは、トランザクションで自分自身をロックする共有場所です。プログラマーに、ロックされたすべてのコードの競合状態で何が起こるかを決定させる代わりに、トランザクションを開始し、Clojureにそのトランザクション内の参照間のすべてのロック状態を処理させます。
また、関連する概念は関数 future
です。私には、将来のオブジェクトは、計算が完了するまで値にまったくアクセスできない同期エージェントとして記述できるように思えます。また、ノンブロッキングAtomとして説明することもできます。これらの未来の正確な概念はありますか?
解決
あなたは本当にClojureを手に入れているようですね。良い仕事:)
Varには「ルートバインディング」があります。すべてのスレッドで表示され、個々のスレッドは、他のスレッドに影響を与えることなく、表示される値を変更できます。私の理解が正しければ、varはすべての人に見えるルートバインディングなしで1つのスレッドだけに存在することはできず、「リバウンド」することはできません最初に(def ...)で定義されるまで。
Refは、変更を囲む(dosync ...)トランザクションの終わりにコミットされますが、トランザクションが一貫した状態で終了できた場合のみです。
他のヒント
Atomsに関するあなたの結論は間違っていると思います:
AtomはVarと似ていますが、値が変更されるまでブロックするスレッド共有の安全性を備えています
原子は swap!
で変更され、低レベルは compare-and-set!
で変更されます。これは何もブロックしません。 swap!
は、1つの参照のみを持つトランザクションのように機能します:
- 古い値はアトムから取得され、スレッドローカルに保存されます
- 関数は古い値に適用され、新しい値を生成します
- これが成功すると、古い値と新しい値でcompare-and-setが呼び出されます。アトムの値が他のスレッドによって変更されていない場合(まだ古い値に等しい)のみ、新しい値が書き込まれます。そうでない場合、操作は最終的に成功するまで(1)から再開します。
質問に2つの問題が見つかりました。
言う:
アクションの実行中にエージェントにアクセスした場合、アクションが完了するまで値は返されません
http://clojure.org/agents のコメント:
エージェントの状態は、スレッドによっていつでもすぐに読むことができます
つまりエージェントの値を取得するために待つ必要はありません(アクションによって変更された値はプロキシされ、アトミックに変更されると思います)。
Agent
の deref
メソッドのコードは次のようになります(SVNリビジョン1382):
public Object deref() throws Exception{
if(errors != null)
{
throw new Exception("Agent has errors", (Exception) RT.first(errors));
}
return state;
}
ブロッキングは含まれません。
また、(Refセクションで)の意味がわかりません
derefへの呼び出しでトランザクションがコミットされます
dosyncブロックのすべてのアクションが完了し、例外がスローされず、トランザクションが再試行される原因が何もない場合、トランザクションはコミットされます。 deref
はそれとは何の関係もないと思いますが、あなたの主張を誤解しているのかもしれません。
Martinは、Atoms操作が1から再開し、最終的に成功するまで言うと正しいです。 スピンウェイティングとも呼ばれます。 ロックを実際にブロックしていることに注意してくださいが、操作が成功するまで操作を行ったスレッドはブロックされるため、非同期操作ではなくブロッキング操作になります。
先物についても、Clojure 1.1は約束と先物の抽象化を追加しました。 約束とは、あるスレッドから別のスレッドに値を配信するために使用できる同期構造です。値が配信されるまで、promiseを間接参照しようとするとブロックされます。
(def a-promise (promise))
(deliver a-promise :fred)
将来は非同期計算を表します。これらは、コードを別のスレッドで実行し、結果を取得する方法です。
(def f (future (some-sexp)))
(deref f) ; blocks the thread that derefs f until value is available
Varには必ずしもルートバインディングがありません。バインドを使用せずに変数を作成することは合法です
(def x)
または
(declare x)
値を持つ前にxを評価しようとすると
Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]