JavaとC#は、配列の境界とポインターの逆参照をチェックすることにより、メモリの安全性を提供します。
競合状態やデッドロックの可能性を防ぐために、プログラミング言語にどのようなメカニズムを実装できますか?
競合は、オブジェクトのエイリアスが同時に発生し、少なくとも1つのエイリアスが変化している場合に発生します。
したがって、競合を防ぐには、これらの条件の1つ以上を偽りにする必要があります。
さまざまなアプローチがさまざまな側面に取り組みます。関数型プログラミングは不変性に重点を置いており、それによって変異性が取り除かれます。ロック/アトミックは同時性を削除します。アフィンタイプはエイリアスを削除します(Rustは変更可能なエイリアスを削除します)。アクターモデルは通常、エイリアシングを削除します。
エイリアスを設定できるオブジェクトを制限することで、上記の条件を回避しやすくなります。そこにチャネルやメッセージパッシングスタイルが含まれます。任意のメモリのエイリアスを作成することはできません。競合のないように配置されたチャネルまたはキューの最後だけです。通常、同時性、つまりロックやアトミックを回避することによって。
これらのさまざまなメカニズムの欠点は、作成できるプログラムが制限されることです。制限がはっきりしないほど、プログラムは少なくなります。そのため、エイリアシングやミュータビリティーは機能せず、理由は簡単ですが、非常に限定的です。
これがRustがそのような混乱を引き起こしている理由です。これは、エイリアシングと可変性をサポートするエンジニアリング言語ですが、エイリアシングと可変性をサポートしますが、それらが同時に発生しないことをコンパイラにチェックさせます。理想的ではありませんが、これにより、従来の多くのプログラムよりも大きなクラスのプログラムを安全に作成できます。
JavaとC#は、配列の境界とポインターの逆参照をチェックすることにより、メモリの安全性を提供します。
最初にC#とJavaがこれを行う方法について考えることは重要です。それらはundefinedの動作を変換することによってそうしますCまたはC++を定義された動作に変換:プログラムをクラッシュさせます。null逆参照と配列インデックスの例外がcaught正しいC#またはJavaプログラムの場合。プログラムにはそのバグがないはずなので、そもそも発生しないはずです。
しかし、それはあなたがあなたの質問で何を意味しているのではないと私は思います!互いに待機しているn個のスレッドがあるかどうかを定期的にチェックし、それが発生した場合にプログラムを終了する「デッドロックセーフ」ランタイムを簡単に作成できますが、満足できるとは思いません。
競合状態やデッドロックの可能性を防ぐために、プログラミング言語にどのようなメカニズムを実装できますか?
私たちがあなたの質問で直面する次の問題は、デッドロックとは異なり、「競合状態」は検出が難しいことです。スレッドセーフの目的は、競合を排除することではないことを思い出してください。私たちが求めているのは、誰がレースに勝っても、プログラムを正しくすることです!競合状態の問題は、2つのスレッドが未定義の順序で実行されており、誰が最初に終了するかわからないことではありません。競合状態の問題は、開発者がいくつかのスレッド終了の順序が可能であることを忘れ、その可能性を説明できないことです。
したがって、質問は基本的に「プログラミング言語が私のプログラムが正しいことを保証できる方法はありますか?」に要約されます。そして、その質問への答えは、実際には違います。
これまでのところ、私はあなたの質問を批判しただけです。ここでギアを切り替えて、あなたの質問の精神に取り組みましょう。言語設計者がマルチスレッドで私たちが直面している恐ろしい状況を緩和するためにできる選択はありますか?
状況は本当に恐ろしいです!マルチスレッドコードを正しく取得することは、特に弱いメモリモデルアーキテクチャでは非常に困難です。なぜそれが難しいのかを考えることは有益です:
したがって、言語デザイナーが物事をより良くすることができる明白な方法があります。 最新のプロセッサのパフォーマンスの勝利を放棄する。マルチスレッドのプログラムであっても、すべてのプログラムに非常に強力なメモリモデルを持たせます。これにより、マルチスレッドプログラムの速度が大幅に低下し、パフォーマンスが向上するため、マルチスレッドプログラムがそもそも存在しない理由に直接作用します。
メモリモデルを別にしても、マルチスレッド化が難しい理由は他にもあります。
その最後の点は、さらに説明があります。 「構成可能」とは、次のことを意味します。
Doubleを指定してintを計算したいとします。計算の正しい実装を記述します。
int F(double x) { correct implementation here }
Intを与えられた文字列を計算したいとします:
string G(int y) { correct implementation here }
ここで、与えられたdoubleで文字列を計算したい場合:
double d = whatever;
string r = G(F(d));
GとFは、より複雑な問題の正しい解に合成できます。
ただし、デッドロックのため、ロックにはこのプロパティがありません。 L1、L2の順序でロックを取得する正しいメソッドM1と、L2、L1の順序でロックを取得する正しいメソッドM2は、誤ったプログラムを作成せずに同じプログラムで使用することはできません。ロックは、「個々のメソッドがすべて正しいので、全体が正しい」とは言えないようにします。
では、言語デザイナーとして何ができるでしょうか?
まず、そこに行かないでください。 1つのプログラムで複数の制御スレッドを使用することは悪い考えであり、スレッド間でメモリを共有することは悪い考えであるため、そもそも言語やランタイムに配置しないでください。
これは明らかにスターターではありません。
次に、より基本的な質問に注意を向けましょう。なぜ最初に複数のスレッドがあるのですか? 2つの主な理由があり、それらは非常に異なりますが、頻繁に同じものに混同されます。どちらもレイテンシの管理に関するものなので、両者は混乱しています。
悪いアイデア。代わりに、コルーチンを介してシングルスレッド非同期を使用します。 C#はこれを美しく行います。 Java、まあまあ。しかしこれは、現在の言語デザイナーがスレッド化問題の解決を支援している主な方法です。 C#のawait
演算子(F#非同期ワークフローおよびその他の先行技術に触発された)は、ますます多くの言語に組み込まれています。
言語設計者は、並列処理でうまく機能する言語機能を作成することで支援できます。たとえば、LINQがPLINQに非常に自然に拡張される方法について考えます。あなたが賢明な人であり、TPL操作を高度に並列でメモリを共有しないCPUバウンド操作に制限する場合、ここで大きな利益を得ることができます。
他に何ができますか?
C#ではロックで待機することはできません。これはデッドロックのレシピだからです。 C#では、値の型をロックすることはできません。これは、常に行うのが間違っているためです。値ではなく、ボックスをロックします。エイリアスは取得/解放のセマンティクスを強制しないため、揮発性のエイリアスを作成すると、C#は警告を出します。コンパイラが一般的な問題を検出して防止する方法は他にもたくさんあります。
C#とJavaは、参照オブジェクトをモニターとして使用できるようにすることにより、大きな設計エラーを引き起こしました。これにより、デッドロックの追跡を困難にし、静的にそれらを防止するのを困難にするあらゆる種類の悪い習慣が奨励されます。そして、すべてのオブジェクトヘッダーのバイトを浪費します。モニターは、モニタークラスから派生する必要があります。
STMは素晴らしいアイデアであり、私はHaskellでのおもちゃの実装をいろいろと試しました。ロックベースのソリューションよりもはるかにエレガントに正しいパーツから正しいソリューションを作成できます。しかし、詳細については、なぜそれを大規模に機能させることができなかったのかについて十分に理解していません。次に会うときはジョー・ダフィーに聞いてください。
プロセス計算ベースの言語については多くの研究が行われており、私はその領域をよく理解していません。自分でいくつかの論文を読んでみて、洞察が得られるかどうかを確認してください。
マイクロソフトでRoslynに勤務した後、Coverityで勤務しました。私がしたことの1つは、Roslynを使用してアナライザーフロントエンドを取得することでした。 Microsoftが提供する正確な字句解析、構文解析、および意味解析を行うことで、一般的なマルチスレッドの問題を検出する検出器を作成するというハードワークに集中できます。
レースやデッドロックなどが発生する根本的な理由は、何をすべきかというプログラムを書いているためです。命令型プログラムの作成はすべてがらくたです。コンピュータはあなたが言うことをします、そして私たちは間違ったことをするようにそれを伝えます。最近のプログラミング言語の多くは、宣言型プログラミングにますます重点を置いています。どのような結果が必要かを言い、その結果を達成するための効率的で安全な正しい方法をコンパイラーに理解させてください。もう一度、LINQについて考えてみましょう。 from c in customers select c.FirstName
、意図を表します。コンパイラーにコードの記述方法を理解させます。
機械学習アルゴリズムは、手作業でコーディングしたアルゴリズムよりも一部のタスクで優れていますが、正確さ、トレーニングにかかる時間、不適切なトレーニングによって生じるバイアスなど、多くのトレードオフがあります。しかし、現在「手動」でコーディングしている非常に多くのタスクが、すぐに機械で生成されたソリューションに対応できるようになる可能性があります。人間がコードを書いていなければ、バグを書いていません。
申し訳ありません。これは巨大で難しいトピックであり、私がこの問題の分野で進展を追い続けてきた20年の間に、PLコミュニティでは明確なコンセンサスが生まれていません。