RustはC ++の同時実行機能からどのように分岐しますか?


35

ご質問

Rustを学ぶために時間を費やすべきかどうかを判断するために、RustがC ++の同時実行機能を根本的かつ十分に改善するかどうかを理解しようとしています。

具体的には、慣用的なRustは、慣用的なC ++の同時実行機能をどのように改善しますか、それとも何らかの相違がありますか?

改善(または相違)はほとんど構文上のものですか、それとも実質的にパラダイムの改善(相違)ですか?それとも別のものですか?それとも、まったく改善(分岐)ではありませんか?


根拠

私は最近、C ++ 14の並行性機能を自分で習おうとしていますが、何かが正しくないと感じています。何か気分が悪い。が気持ち悪い?言いにくい。

並行処理に関しては、コンパイラが正しいプログラムの作成を実際に支援しようとしていないように感じます。コンパイラではなくアセンブラを使用しているように感じます。

確かに、並行性に関しては、微妙で誤った概念にまだ悩まされている可能性があります。たぶん、私はまだ、ステートフルプログラミングとデータレースの間のBartosz Milewskiの緊張感を理解していません。たぶん、コンパイラーにどれだけの健全な並行方法論があり、OSにどれくらいあるのか、私にはよくわかりません。

回答:


56

Rustプロジェクトの主な目標の1つは、より良い並行性のストーリーです。そのため、プロジェクトがその目標を達成することを信頼していれば、改善が期待されます。完全な免責事項:私はRustについて高い意見を持ち、それに投資しています。要求に応じて、価値判断を避け、(IMHO)改善ではなく違いを説明しようとします。

安全な錆と安全でない錆

「Rust」は2つの言語で構成されています。1つは、システムプログラミングの危険からあなたを隔離しようとする言語、もう1つはそのような願望のないより強力な言語です。

Unsafe Rustは、C ++によく似た厄介で野bruな言語です。任意に危険なことをしたり、ハードウェアに話しかけたり、手動でメモリを(誤って)管理したり、足で自分自身を撃ったりすることができます。そしてそれに関与する他のすべてのプログラマーの手。キーワードを使用してこの言語を選択するunsafeと、CおよびC ++の場合と同様に、1つの場所で1つのミスをするとプロジェクト全体がクラッシュする可能性があります。

Safe Rustは「デフォルト」であり、大部分のRustコードは安全でありunsafe、コード内でキーワードに言及しない限り、安全な言語を離れることはありません。投稿の残りの部分は、ほとんどの場合、その言語に関係しunsafeます。なぜなら、コードは、安全なRustが非常に懸命にあなたに与えるというすべての保証を破ることができるからです。反対に、unsafeコードはではなく、コミュニティによってそのように扱われません(ただし、必要でない場合は強く推奨されません)。

危険ですが、安全なコードが使用する抽象化を構築できるため、重要です。優れた安全でないコードは、型システムを使用して他の人がそれを誤用するのを防ぎます。したがって、Rustプログラムに安全でないコードが存在しても、安全なコードを妨害する必要はありません。Rustの型システムにはC ++にはないツールがあり、同時実行抽象化を実装する安全でないコードはこれらのツールを効果的に使用するため、以下の違いがすべて存在します。

差異なし:共有/可変メモリ

Rustはメッセージの受け渡しをより重視し、共有メモリを厳密に制御しますが、共有メモリの同時実行性を排除せず、共通の抽象化(ロック、アトミック操作、条件変数、同時コレクション)を明示的にサポートします。

さらに、C ++と同様に、関数型言語とは異なり、Rustは従来の命令型データ構造が本当に好きです。標準ライブラリには永続的/不変のリンクリストはありません。ありますが、C ++ std::collections::LinkedListのようなものstd::listで、同じ理由で推奨されませんstd::list(キャッシュの不適切な使用)。

ただし、このセクションのタイトル(「共有/変更可能なメモリ」)を参照すると、RustにはC ++との違いが1つあります。時間。いわば「自分のスレッドのプライバシー」で好きなようにメモリを変更します。これを、共有可変メモリがデフォルトオプションであり、広く使用されているC ++と比較してください。

shared-xor-mutableパラダイムは以下の違いにとって非常に重要ですが、慣れるまでに時間がかかり、大きな制限を課すまったく異なるプログラミングパラダイムでもあります。たとえば、アトミック型(AtomicUsize共有可変メモリの本質)など、このパラダイムをオプトアウトする必要がある場合があります。ロックは、同時読み取りおよび書き込みを除外するため、共有xor-mutableルールにも従うことに注意してください(1つのスレッドが書き込みを行っている間、他のスレッドは読み取りまたは書き込みを行えません)。

差異なし:データの競合は未定義の動作(UB)

Rustコードでデータレースをトリガーすると、C ++と同様にゲームオーバーになります。すべての賭けはオフになっており、コンパイラは好きなようにできます。

ただし、安全なRustコードにデータの競合(またはその点でのUB)がないことは強固な保証です。これは、コア言語と標準ライブラリの両方に拡張されます。unsafeUBをトリガーする(サードパーティライブラリを含むが標準ライブラリを除く)Rustプログラムを作成できる場合、それはバグと見なされ、修正されます(これは既に何度か発生しています)。もちろん、これは、プログラムをUBで書くのは簡単なC ++とはまったく対照的です。

違い:厳格なロック規則

C ++とは異なり、錆(内ロックstd::sync::Mutexstd::sync::RwLockなど)を所有している、それは保護のデータを。ロックを取得し、ドキュメントでのみロックに関連付けられている共有メモリを操作する代わりに、ロックを保持していない間は共有データにアクセスできません。RAIIガードはロックを保持し、同時にロックされたデータへのアクセスを許可します(これはC ++で実装できますが、std::ロックではありません)。ライフタイムシステムにより、ロックを解除した後(RAIIガードを削除した後)データにアクセスし続けることができなくなります。

もちろん、有用なデータを含まないロック(Mutex<()>)を持つことができ、そのロックに明示的に関連付けずにメモリを共有することができます。ただし、潜在的に非同期の共有メモリを使用するにはが必要unsafeです。

違い:偶発的な共有の防止

共有メモリを持つことはできますが、明示的に要求した場合にのみ共有します。たとえば、メッセージの受け渡し(からのチャネルなどstd::sync)を使用する場合、ライフタイムシステムは、データを別のスレッドに送信した後、データへの参照を保持しないようにします。ロックの背後でデータを共有するには、ロックを明示的に構築し、別のスレッドに渡します。非同期メモリを共有するにunsafeは、を使用する必要がありますunsafe

これは次のポイントに結びついています。

違い:スレッドセーフトラッキング

Rustの型システムは、スレッドセーフの概念を追跡します。具体的には、Sync特性は、データ競合のリスクなしに複数のスレッドで共有できるタイプを示し、一方Send、あるスレッドから別のスレッドに移動できるタイプをマークします。これは、プログラム全体でコンパイラーによって強制されるため、ライブラリー設計者は、これらの静的チェックがなければ愚かなほど危険な最適化を敢行します。たとえばstd::shared_ptr、参照カウントを操作するために常にアトミック操作を使用するC ++ は、shared_ptrたまたま複数のスレッドで使用される場合にUBを回避します。錆はありRcかつArc、そののみが異なるたRc 非アトミック参照カウント操作の使用とスレッドセーフではありません(つまり、実装していないSyncか、Send一方で)Arc非常に似ていますshared_ptr (および両方の特性を実装します)。

unsafe同期を手動で実装するために使用しない場合、特性の有無は正しく推測されることに注意してください。

違い:非常に厳しいルール

一部のコードにデータ競合やその他のUBがないことをコンパイラーが完全に確認できない場合、期間はコンパイルされません。前述のルールや他のツールを使用すればかなり遠くまで行くことができますが、遅かれ早かれ、正しいことをしたいと思うでしょうが、コンパイラーの注意を逃れる微妙な理由があります。ロックフリーのトリッキーなデータ構造である可能性がありますが、「共有配列のランダムな場所に書き込みますが、インデックスはすべての場所が1つのスレッドのみによって書き込まれるように計算される」という平凡なものにもなります。

その時点で、弾丸を噛んで不要な同期を少し追加するか、コンパイラーがその正確性を確認できるようにコードを言い換えることができます(多くの場合、実行可能、時には非常に困難、時には不可能)unsafe。それでも、それは余分なメンタルオーバーヘッドであり、Rustはunsafeコードの正確性については保証しません。

違い:少ないツール

前述の違いのため、Rustでは、データの競合(または解放後の使用、二重解放、または...)が発生する可能性のあるコードを記述することは非常にまれです。これは素晴らしいことですが、そのようなエラーを追跡するためのエコシステムは、若さとコミュニティの規模が小さいことを考えると、予想よりもさらに開発が遅れているという不幸な副作用があります。

valgrindやLLVMのスレッドサニタイザーなどのツールは原則としてRustコードに適用できますが、これが実際に機能するかどうかはツールによって異なります(特に機能するものは、 -それを行う方法に関する日付のリソース)。Rustが現在、実際の仕様、特に正式なメモリモデルに欠けていることは、実際には役に立ちません。

要するに、unsafeRustコードを正しく記述することは、C ++コードを正しく記述することより困難です。ただし、両方の言語は機能とリスクの点でほぼ同等です。もちろん、これは、典型的なRustプログラムに含まれるunsafeコードの割合が比較的小さいのに対し、C ++プログラムは完全にC ++ であるという事実に対して重み付けする必要があります。


6
私の画面のどこに+25の賛成票スイッチがありますか?見つからない!この有益な回答は大歓迎です。それがカバーする点に関して明確な質問は私に残しません。だから、他のポイントに:Rustのドキュメントを理解している場合、Rustには[a]統合テスト機能と[b] Cargoというビルドシステムがあります。これらは合理的に生産準備ができていますか?また、Cargoについては、ビルドプロセスにシェル、PythonおよびPerlスクリプト、LaTeXコンパイルなどを追加できるようにすることについて、ユーモアがありますか?
-thb

2
@thbテスト用のものは非常に必要最低限​​(例、モックなし)ですが機能的です。Cargoは非常にうまく機能しますが、Rustと再現性に重点を置いているため、ソースコードから最終成果物までのすべてのステップをカバーするのに最適なオプションではない可能性があります。ビルドスクリプトを書くことはできますが、それはあなたが言及するすべてのものに適切ではないかもしれません。(ただし、人々は定期的にビルドスクリプトを使用してCライブラリをコンパイルしたり、既存のバージョンのCライブラリを見つけたりするため、純粋なRustを使用するとCargoが動作しなくなることはありません。)

2
ちなみに、それが価値があるものについては、あなたの答えはかなり決定的に見えます。私はC ++が好きなので、C ++には必要なほぼすべての機能が揃っているため、C ++は安定しており、広く使用されているため、これまでのあらゆる非軽量の目的にC ++を使用することに非常に満足しています(Javaに興味を持ったことがありません) 、 例えば)。しかし、今、私たちは、並行性を持っており、C ++ 14には思える私にそれで苦労すること。私は10年前に新しいプログラミング言語を自発的に試したことはありませんが、(Haskellがより良い選択肢に見えない限り)Rustを試さなければならないと思います。
thb

Note that if a type doesn't use unsafe to manually implement synchronization, the presence or absence of the traits are inferred correctly.実際には、unsafe要素に対しても同様です。生のポインタだけではなく、デフォルトでそれらを含む構造体もどちらも持たないSyncことShareを意味します。
ハウレス

ŁukaszNiemier@大丈夫動作するように起こることができますが、安全ではない、使用してタイプを巻き上げることが可能な億の方法があるSendSyncさえ、それは本当にいけないけれどもが。

-2

RustもErlang and Goによく似ています。バッファと条件付き待機を備えたチャネルを使用して通信します。Goと同じように、共有メモリの実行、アトミックな参照カウントとロックのサポート、スレッドからスレッドへのチャネルの受け渡しにより、Erlangの制限を緩和します。

ただし、Rustはさらに一歩進んでいます。Goはあなたに正しいことをすることを信頼しますが、Rustはあなたと一緒に座っているメンターを割り当て、あなたが間違ったことをしようとすると文句を言います。Rustの指導者はコンパイラです。高度な分析を実行して、スレッドに渡される値の所有権を判断し、潜在的な問題がある場合はコンパイルエラーを提供します。

以下はRUSTドキュメントからの引用です。

所有権ルールは、安全な同時コードを記述するのに役立つため、メッセージ送信で重要な役割を果たします。同時プログラミングのエラーを防ぐことは、Rustプログラム全体で所有権について考える必要があるというトレードオフを行うことで得られる利点です。—値の所有権を持つメッセージの受け渡し。

Erlangが過酷で、Goがフリー状態の場合、Rustは乳母状態です。

プログラミング言語の並行性イデオロギー、Java、C#、C、C +、Go、およびRustから詳細情報を見つけることができます。


2
Stack Exchangeへようこそ!自分のブログにリンクするときは常に、その旨を明示する必要があることに注意してください。ヘルプセンターをご覧ください。
グローフィンデル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.