JavaとC#は、配列の境界とポインターの逆参照をチェックすることにより、メモリの安全性を提供します。
競合状態やデッドロックの可能性を防ぐために、プログラミング言語にどのようなメカニズムを実装できますか?
JavaとC#は、配列の境界とポインターの逆参照をチェックすることにより、メモリの安全性を提供します。
競合状態やデッドロックの可能性を防ぐために、プログラミング言語にどのようなメカニズムを実装できますか?
回答:
競合は、オブジェクトのエイリアスが同時にあり、少なくとも1つのエイリアスが変化している場合に発生します。
したがって、競合を防ぐには、これらの条件の1つ以上を偽りにする必要があります。
さまざまなアプローチがさまざまな側面に取り組みます。関数型プログラミングは不変性に重点を置いており、それによって変異性が取り除かれます。ロック/アトミックは同時性を削除します。アフィンタイプはエイリアスを削除します(Rustは変更可能なエイリアスを削除します)。アクターモデルは通常、エイリアシングを削除します。
エイリアスを設定できるオブジェクトを制限することで、上記の条件を回避しやすくなります。そこにチャネルやメッセージパッシングスタイルが含まれます。任意のメモリにエイリアスを設定することはできません。競合のないように配置されたチャネルまたはキューの最後だけをエイリアス化することはできません。通常、同時性、つまりロックやアトミックを回避することによって。
これらのさまざまなメカニズムの欠点は、作成できるプログラムが制限されることです。制限がはっきりしないほど、プログラムは少なくなります。したがって、エイリアシングやミュータビリティーは機能せず、理由は簡単ですが、非常に限定的です。
それが、Rustがそのような動揺を引き起こしている理由です。エイリアシングとミュータビリティをサポートするエンジニアリング言語です(アカデミック言語とは異なります)が、それらが同時に発生しないことをコンパイラチェックします。理想的ではありませんが、これまでの多くのプログラムよりも大きなクラスのプログラムを安全に作成できます。
JavaとC#は、配列の境界とポインターの逆参照をチェックすることにより、メモリの安全性を提供します。
C#とJavaがこれをどのように行うかを最初に考えることが重要です。これは、CまたはC ++での未定義の動作を定義済みの動作に変換することによって行います。プログラムをクラッシュさせます。Null逆参照と配列インデックスの例外は、正しい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、まあまあ。しかし、これが現在の言語デザイナーたちがスレッドの問題を解決するのに役立つ主な方法です。await
C# のオペレーター(F#非同期ワークフローやその他の先行技術に触発された)は、ますます多くの言語に組み込まれています。
言語設計者は、並列処理でうまく機能する言語機能を作成することで支援できます。たとえば、LINQがPLINQに非常に自然に拡張される方法について考えます。あなたが賢明な人であり、TPL操作を高度に並列でメモリを共有しないCPUにバインドされた操作に制限する場合、ここで大きな利益を得ることができます。
他に何ができますか?
C#ではロックで待機することはできません。これはデッドロックのレシピだからです。C#では、値の型をロックすることはできません。これは、常に行うのが間違っているためです。値ではなく、ボックスをロックします。エイリアスは取得/解放のセマンティクスを強制しないため、揮発性のエイリアスを作成すると、C#は警告を出します。コンパイラが一般的な問題を検出して防止する方法は他にもたくさんあります。
C#とJavaは、参照オブジェクトをモニターとして使用できるようにすることで、大きな設計エラーを引き起こしました。これにより、デッドロックの追跡が困難になり、静的に防止することが困難になる、あらゆる種類の悪い習慣が奨励されます。そして、それはすべてのオブジェクトヘッダーのバイトを浪費します。モニターは、モニタークラスから派生する必要があります。
STMは素晴らしいアイデアであり、私はHaskellでのおもちゃの実装をいろいろと試しました。ロックベースのソリューションよりもはるかにエレガントに正しいパーツから正しいソリューションを作成できます。しかし、詳細については、なぜそれを大規模に機能させることができなかったのかについて十分に理解していません。次に会うときはジョー・ダフィーに聞いてください。
プロセス計算ベースの言語については多くの研究が行われており、私はその領域をよく理解していません。自分でいくつかの論文を読んでみて、洞察が得られるかどうかを確認してください。
MicrosoftでRoslynに携わった後、Coverityで働きました。私がしたことの1つは、Roslynを使用してアナライザーフロントエンドを取得することでした。Microsoftが提供する正確な字句解析、構文解析、および意味解析を行うことで、一般的なマルチスレッドの問題を検出する検出器を作成するというハードワークに集中できます。
私たちが人種やデッドロックなどを抱えている根本的な理由は、何をすべきかを示すプログラムを書いているためです。コンピュータはあなたが言うことをします、そして私たちは間違ったことをするようにそれを伝えます。最近のプログラミング言語の多くは、宣言型プログラミングにますます重点を置いています。どのような結果を望んでいるかを言い、その結果を達成するための効率的で安全な正しい方法をコンパイラーに理解させてください。もう一度、LINQについて考えてみましょう。意図from c in customers select c.FirstName
を表現したいのです。コンパイラーにコードの記述方法を理解させます。
機械学習アルゴリズムは、手作業でコーディングしたアルゴリズムよりも一部のタスクで優れていますが、正確さ、トレーニングにかかる時間、不適切なトレーニングによって生じるバイアスなど、多くのトレードオフがあります。しかし、現在「手動」でコーディングしている非常に多くのタスクが、すぐに機械で生成されたソリューションに対応できるようになる可能性があります。人間がコードを書いていなければ、バグを書いていません。
申し訳ありませんが少し混乱していました。これは巨大で難しいトピックであり、私がこの問題の分野で進展を追い続けてきた20年の間に、PLコミュニティでは明確なコンセンサスが生まれていません。